JAVA学习笔记之线程————根据传智播客学习视频总结出来

时间:2022-04-07 12:31:39

JAVA线程

在学习JAVA的过程中,肯定要学线程方面的内容,记此笔记来记录一些自己学习到的知识以及一些体会。

1.线程与进程

进程指的是一个在内存中运行的程序,每个进程都在内存中有一个自己的内存空间,比如打开WINDOWS内存管理器,就会看到一系列进程,它们都在内存中有自己的空间。而线程是进程的一个执行的任务,一个进程中可以有多个线程,线程之间可以共享数据。JAVA程序的进程中,至少有两个线程,主线程跟垃圾回收线程。一个进程至少有一个线程,为了提高效率可以在一个线程中开启多个线程,如多线程下载。

线程与进程的区别:

1,进程有自己的存放空间,数据存放空间(栈空间和堆空间)是独立的。 2,线程的堆空间是共享的,栈空间是独立的,线程之间可以相互影响。

2.创建线程的方式

(1),继承Thread类:
子类复写父类的run方法,把运行的代码放入run方法中。
建立子类的时候,线程也被创建。
调用start方法。

package gyc.study.thread;
import java.lang.Thread;

class Thread1 extends Thread
{

public void run ()
{
for(int i = 0 ; i<100;i++)
{
System.out.println( "Thread1----->"+ i);
}
}
}

public class TestProduceerConsumerDemo1 {
public static void main(String[] args)
{
Thread1 th1 = new Thread1();
th1.start();
for(int i = 0 ; i<100;i++)
{
System.out.println("mianthread--->" + i);
}
}
}

运行程序我们可以看出每次运行的结果是不一样的,这是因为CPU分配执行线程的问题,这也说明了线程运行的随机性。
不可以直接调用线程对象的run方法,这样仅仅是代表程序调用run方法,而不是启动线程。

(2),实现Runnable接口:

子类覆盖接口中的run方法。

通过Thread类创建线程,参数为实现Runnable接口的类。

Thread类调用start方法。

可以通过匿名类来实现。

package gyc.study.thread;
import java.lang.Thread;

class Thread2 implements Runnable
{

public void run ()
{
for(int i = 0 ; i<100;i++)
{
System.out.println( Thread.currentThread().getName()+ i); //获取当前线程的名字
}
}
}

public class TestProduceerConsumerDemo2 {
public static void main(String[] args)
{
new Thread(new Thread2(),"日和").start();
for(int i = 0 ; i<100;i++)
{
System.out.println("mianthread--->" + i);
}
}
}

(3),两种方式的比较:

继承方法简单,但是不可以同时继承其他类(JAVA单继承性),并且同分资源不共享资源。

实现接口方式同分资源可以共享,而且可以同时继承其他类,推荐使用。

下面的例子说明了继承方法同分资源不共享而实现接口的方式则共享。

package gyc.study.thread;
import java.lang.Thread;

class Thread3 implements Runnable
{

private int number = 50;
public void run ()
{
for(int i = 0 ; i<100;i++)
{
if(number>0)
{
System.out.println( Thread.currentThread().getName()+ "---->"+ number); //获取当前线程的名字
number--;
}
}
}
}
class Thread4 extends Thread
{
private int number = 50;
Thread4(String name)
{
this.setName(name);
}
public void run ()
{
for(int i = 0 ; i<100;i++)
{
if(number>0)
{
System.out.println( Thread.currentThread().getName()+ "---->"+ number); //获取当前线程的名字
number--;
}
}
}
}
public class TestProduceerConsumerDemo3 {
public static void main(String[] args)
{

Thread4 th41 = new Thread4("冷笑话1");
Thread4 th42 = new Thread4("冷笑话2");
th41.start();
th42.start();
Runnable r1 = new Thread3();
new Thread(r1,"日和1").start();
new Thread(r1,"日和2").start();

}
}

如果启动线程的时候,新的线程对象调用的实现Runnable接口的类是同一个的话,那么类的属性是可以共用的。

3,线程的生命周期

线程的生命周期氛围,新建,就绪,阻塞,执行,死亡。新建:当程序新建一个线程对象的时候,该线程就是新建状态,JAVA虚拟机会分配内存跟成员变量的值Thread t1 = new Thread();就绪:当对象调用start方法的时候就进入了就绪状态,这时候不会立即执行线程,需要等待CPU为其分配资源。t1.start();运行:当线程获得到了CPU的资源,开始执行run里面的内容。阻塞:当线程需要被中断的时候,让出CPU资源,阻塞状态不能直接回到就绪状态,只能回到新建状态。死亡:run方法中的代码执行完毕会线程会死亡。当线程出现异常的时候也会死亡。调用线程的stop方法也会让线程死亡(但是这种方法容易造成死锁,不推荐)。如果一个线程启动之后便拥有和主线程一样的地位,不会因为主线程的死亡而死亡。如果想判断线程是否死亡可以调用isAlive方法,当线程处于就绪,运行,阻塞状态时后返回true,当线程处于新建和死亡状态的时候返回false。已经死亡的线程不可以再此启动。

4,控制线程的方法

(1),join方法:调用join方法后,线程对象会强制执行,在执行期间,其他的线程无法运行,必须等到该线程结束后才可以运行。(2),setDaemon方法:设置线程为后台线程。后台线程是处于后台运行,为其他的线程提供服务,JAVA的垃圾回收线程就是后台线程,后台线程也称守护线程或精灵线程。当前台前程都死亡后,后台线程自动死亡。当一个线程调用setDaemon(tere)后,该线程就是后台线程。调用setDaemon(tere)方法时,必须在该线程调用strat后,否则会出现IllegalThreadStateException异常。前台创建的线程默认是前台线程,判断一个线程是否是后台线程可以调用isDaemon()方法。(3),sleep方法线程休眠的方法。让线程进入阻塞状态。sleep(long milllis) throws InterruptedException:毫秒。在这期间,该线程不会获得执行的机会。(4)yield方法线程让步,暂停当前执行的线程对象,让其他线程执行。并不会让线程进入阻塞状态,而是进入就绪状态所以完全有可能再次执行。(5)过时的方法stop():让线程马上死亡,并且释放所有的锁,无法保证内部对象是否正确,且容易造成死锁。suspend():线程挂起,不会获得执行机会,如果该线程拥有一个重要的资源,那么在调用resume之前,没有线程可以获得该资源。resume():恢复挂起的线程。

5,线程的优先级

每个线程都有优先级,优先级越高越容易获得执行的机会,并非越高就一定会执行,执行与否还是取决于CPU调用。可以通过setPriority(int x)和getPriority()来设置和获得优先级。MAX_PRIORITY,*值是10。MIN_PRIORITY最低级值是1。NORM_PRIORITY默认优先级值是5。

6,线程的安全问题

(1)导致线程出现安全问题的原因:多线程访问的延迟性。线程线程的随机性。我们可以通过sleep方法来模拟访问的延迟性。
package gyc.study.thread;
import java.lang.Thread;

class Acount
{
private double money;
public Acount(double money)
{
this.money = money;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}

}
class Thread41 implements Runnable
{

private double number ;
private Acount a;
public Thread41(double number , Acount a)
{
this.number = number;
this.a = a;
}
public void run ()
{
for(int i = 0 ; i<100;i++)
{
if(a.getMoney()>0)
{
try {
Thread.currentThread().sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println( Thread.currentThread().getName()+ "取出"+ number); //获取当前线程的名字
a.setMoney(a.getMoney() - number);
System.out.println( "剩余"+ a.getMoney());
}
}
}
}

public class TestProduceerConsumerDemo4 {
public static void main(String[] args)
{
Acount a1 = new Acount(1000);
new Thread(new Thread41(800,a1),"日和1").start();
new Thread(new Thread41(800,a1),"日和2").start();

}
}

可以看出,出现了线程安全的问题。
(2)解决方式:使用同步代码块synchronized(同步监听对象){
执行代码}
package gyc.study.thread;
import java.lang.Thread;

class Acount
{
private double money;
public Acount(double money)
{
this.money = money;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}

}
class Thread41 implements Runnable
{

private double number ;
private Acount a;
public Thread41(double number , Acount a)
{
this.number = number;
this.a = a;
}
public void run ()
{
synchronized (a) {
for(int i = 0 ; i<100;i++)
{
if(a.getMoney()>0)
{
try {
Thread.currentThread().sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println( Thread.currentThread().getName()+ "取出"+ number); //获取当前线程的名字
a.setMoney(a.getMoney() - number);
System.out.println( "剩余"+ a.getMoney());
}
}
}
}
}

public class TestProduceerConsumerDemo4 {
public static void main(String[] args)
{
Acount a1 = new Acount(1000);
new Thread(new Thread41(800,a1),"日和1").start();
new Thread(new Thread41(800,a1),"日和2").start();

}
}

若同步对象是this的话,那么只能只有实现Runnable接口的类才能使用,因为如果是使用继承的方法的话,那么就没有共享资源,有几个线程的话就有几个this。
使用同步方法
在方法前synchronized来修饰没有用synchronized的代码,出现线程安全问题
package gyc.study.thread;
import java.lang.Thread;

class Acount
{
private double money;
public Acount(double money)
{
this.money = money;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
public void draw(double number)
{
if(getMoney()>0)
{
try {
Thread.currentThread().sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println( Thread.currentThread().getName()+ "取出"+ number); //获取当前线程的名字
setMoney(getMoney() - number);
System.out.println( "剩余"+ getMoney());
}
}
}
class Thread41 implements Runnable
{

private double number ;
private Acount a;
public Thread41(double number , Acount a)
{
this.number = number;
this.a = a;
}
public void run ()
{
for(int i = 0 ; i<100;i++)
{
a.draw(number);
}

}
}

public class TestProduceerConsumerDemo4 {
public static void main(String[] args)
{
Acount a1 = new Acount(1000);
new Thread(new Thread41(800,a1),"日和1").start();
new Thread(new Thread41(800,a1),"日和2").start();

}
}

使用之后,线程安全。
package gyc.study.thread;
import java.lang.Thread;

class Acount
{
private double money;
public Acount(double money)
{
this.money = money;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
public synchronized void draw(double number)
{
if(getMoney()>0)
{
try {
Thread.currentThread().sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println( Thread.currentThread().getName()+ "取出"+ number); //获取当前线程的名字
setMoney(getMoney() - number);
System.out.println( "剩余"+ getMoney());
}
}
}
class Thread41 implements Runnable
{

private double number ;
private Acount a;
public Thread41(double number , Acount a)
{
this.number = number;
this.a = a;
}
public void run ()
{
for(int i = 0 ; i<100;i++)
{
a.draw(number);
}

}
}

public class TestProduceerConsumerDemo4 {
public static void main(String[] args)
{
Acount a1 = new Acount(1000);
new Thread(new Thread41(800,a1),"日和1").start();
new Thread(new Thread41(800,a1),"日和2").start();

}
}

同步方法的监听器对象有两种
1,如果方法是静态的,那么同步对象就是所在类Acount.class(一份字节码,在程序中唯一);2,如果方法是非静态的,那么同步对象就是this当用synchronized来修饰run方法的时候,实现Ruannable接口的类是可以实现线程安全的,只要他们的对象是一个,但是继承Thread方式实现的类是不可以实现线程安全的,因为他们的对象是多个。具体可以参照共享资源方面来理解。(3)创造线程安全的单例模式单例模式有懒汉式和饿汉式,饿汉式是安全的,如何使懒汉式实现线程安全呢?下面是懒汉式的代码
class Stample
{
private static Stample s;
private Stample()
{
super();
}
public static Stample getStample()
{
if(s==null)
{
s = new Stample();
}
return s;
}
}

如果这时候,线程同时进入if(s==null)语句,那么此时s为空,所以两个线程都进入下面语句,
所以创建了两个Stample对象。此时,修改代码如下
class Stample
{
private static Stample s;
private Stample()
{
super();
}
public static Stample getStample()
{
synchronized (Stample.class) {
if(s==null)
{
s = new Stample();
}
}
return s;
}
}

加入同步代码块中虽然解决了同步问题,但是每次都会在同步代码块中进行s是否为空的判断,
代码没有效率,但是如果把if(s==null)移除同步代码块的的话,则会出现之前的现象,所以应该判断两次,修改后的代码如下
class Stample
{
private static Stample s;
private Stample()
{
super();
}
public static Stample getStample()
{
if(s==null){
synchronized (Stample.class) {
if(s==null)
{
s = new Stample();
}
}
}
return s;
}
}

7,线程的锁

锁提供了比synchronized方法跟代码块更广泛的锁定操作。通常使用ReentrantLock(可重入锁)来实现控制。ReentrantLock没有监听对象。先创建private final ReentrantLock lock = new ReentrantLock();然后在需要进行同步的方法加锁即可。(不能修饰run方法)代码如下
package gyc.study.thread;
import java.lang.Thread;
import java.util.concurrent.locks.ReentrantLock;

class Acount2
{
private double money;
public Acount2(double money)
{
this.money = money;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
private final ReentrantLock lock = new ReentrantLock();
public void draw(double number)
{
lock.lock();
try
{
if(getMoney()>0)
{
Thread.currentThread().sleep(1);
System.out.println( Thread.currentThread().getName()+ "取出"+ number); //获取当前线程的名字
setMoney(getMoney() - number);
System.out.println( "剩余"+ getMoney());
}
}
catch(InterruptedException e)
{
e.printStackTrace();
}

finally
{
lock.unlock();
}
}
}
class Thread61 implements Runnable
{

private double number ;
private Acount2 a;
public Thread61(double number , Acount2 a)
{
this.number = number;
this.a = a;
}
public void run ()
{
for(int i = 0 ; i<100;i++)
{
a.draw(number);
}

}
}

public class TestProduceerConsumerDemo6 {
public static void main(String[] args)
{
Acount2 a1 = new Acount2(1000);
new Thread(new Thread61(800,a1),"日和1").start();
new Thread(new Thread61(800,a1),"日和2").start();

}
}

死锁的问题
两个线程相互等待对方释放监听器而出现的问题。例子如下
package gyc.study.thread;
import java.lang.Thread;
import java.util.concurrent.locks.ReentrantLock;

class Acount3
{

}
class Thread71 implements Runnable
{
private Acount3 a3;
public Thread71(Acount3 a3)
{
this.a3 =a3;
}
public void run ()
{
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized (a3) {
System.out.println("111111");
}
}
}
public class TestProduceerConsumerDemo7 {
public static void main(String[] args)
{
Acount3 a3 = new Acount3();
Thread t1 = new Thread(new Thread71(a3));
t1.start();
synchronized(a3)
{
try {
t1.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("22222");
}
}
}

因为主线程获取到了a3资源,但是如果想继续执行,则必须等到t1线程执行结束,但是t1线程想继续执行则需要
主线程的a3资源,这时候产生了死锁。

8,线程通信

wait()方法,让当前线程放弃同步监听对象,进入等待,直到其他线程调用同一个监听对象的notify()方法或notifyAll()方法。notify()方法,唤醒同一个同步监听对象调用wait()方法的第一个线程。
notifyAll()方法,唤醒同一个同步监听对象调用wait()方法的所有线程。
可以解决生产者消费者问题,一替一换的打印代码如下。
package gyc.study.thread;
import java.lang.Thread;
import java.util.concurrent.locks.ReentrantLock;

class Jump
{
private String book;
private String wirter;
boolean flag = false;
public synchronized void set(String book,String wirter)
{
if(!flag)
{
setBook(book);
try {
Thread.currentThread().sleep(1);
setWirter(wirter);
flag=true;
this.notify();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

}
public synchronized void get()
{
if(flag)
{
System.out.println(book + "," +wirter);
flag=false;
this.notify();
}
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

}
public String getBook() {
return book;
}
public void setBook(String book) {
this.book = book;
}
public String getWirter() {
return wirter;
}
public void setWirter(String wirter) {
this.wirter = wirter;
}

}
class Writerjunp implements Runnable
{

@Override
public void run() {
for(int i=0;i<5000;i++)
{

if(i%2 == 0)
{
jump.set("海贼王","尾田荣一郎");
}
else
{
jump.set("灌篮高手","井上雄彦");
}

}

}
private Jump jump;
public Writerjunp(Jump jump)
{
this.jump = jump;
}
}
class Readrjunp implements Runnable
{

@Override
public void run() {
for(int i=0;i<2000;i++)
{
jump.get();
}

}
private Jump jump;
public Readrjunp(Jump jump)
{
this.jump = jump;
}
}
public class TestProduceerConsumerDemo8 {
public static void main(String[] args)
{
Jump jump = new Jump();
new Thread(new Writerjunp(jump)).start();
new Thread(new Readrjunp(jump)).start();
}
}

这三个方法属于Object而不是Thread,必须用同一个同步监听对象来调用这三个方法。
如果说用的是Lock模式的话,可以通过lock来创建Condition对象,使用Condition的await()方法,signal()方法,signalAll()方法,来实现功能。具体代码如下
package gyc.study.thread;
import java.lang.Thread;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

class Jump2
{
private String book;
private String wirter;
boolean flag = false;
private final ReentrantLock lock = new ReentrantLock();
private final Condition con = lock.newCondition();
public void set(String book,String wirter)
{
lock.lock();
try
{
if(!flag)
{
setBook(book);
Thread.currentThread().sleep(1);
setWirter(wirter);
flag=true;
con.signal();

}
con.await();
}
catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
finally
{
lock.unlock();
}
}
public void get()
{
lock.lock();
try{
if(flag)
{
System.out.println(book + "," +wirter);
flag=false;
con.signal();
}
con.await();
}
catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
finally
{
lock.unlock();
}

}
public String getBook() {
return book;
}
public void setBook(String book) {
this.book = book;
}
public String getWirter() {
return wirter;
}
public void setWirter(String wirter) {
this.wirter = wirter;
}

}
class Writerjunp2 implements Runnable
{

@Override
public void run() {
for(int i=0;i<5000;i++)
{

if(i%2 == 0)
{
jump.set("海贼王","尾田荣一郎");
}
else
{
jump.set("灌篮高手","井上雄彦");
}

}

}
private Jump2 jump;
public Writerjunp2(Jump2 jump)
{
this.jump = jump;
}
}
class Readrjunp2 implements Runnable
{

@Override
public void run() {
for(int i=0;i<2000;i++)
{
jump.get();
}

}
private Jump2 jump;
public Readrjunp2(Jump2 jump)
{
this.jump = jump;
}
}
public class TestProduceerConsumerDemo9 {
public static void main(String[] args)
{
Jump2 jump2 = new Jump2();
new Thread(new Writerjunp2(jump2)).start();
new Thread(new Readrjunp2(jump2)).start();
}
}

9.问题
在书写此篇博客中,代码均为手打,过程中出现了一些问题。1,sleep位置错误,一定要在set两个对象之间调用2,等待跟唤醒的逻辑一定要搞清楚。3,注意Condition对象的方法。4,同步监听对象一定要清楚。5,双核处理器处理多线程程序可能不能得到想要的效果,请对比来看。本人才疏学浅,希望大家多多指教,在此谢谢各位了。