一、创建多线程程序的第一种方式: 继承(extends) Thread类 

  Thread类的子类: MyThread

    //1.创建一个Thread类的子类
public class MyThread extends Thread{
//2.在Thread类的子类中重写Thread类中的run方法,设置线程任务(开启线程要做什么?)
@Override
public void run() {
for (int i = 0; i <20 ; i++) {
System.out.println("run:"+i);
}
}
}

  主线程: MyThread

public class MainThread {
public static void main(String[] args) {
//3.创建Thread类的子类对象
MyThread mt = new MyThread();
//4.调用Thread类中的方法start方法,开启新的线程,执行run方法
mt.start();
for (int i = 0; i <20 ; i++) {
System.out.println("main:"+i);
}
}
}

结果:随机性打印

main:0
run:0
main:1
run:1
main:2
run:2
main:3
run:3
main:4
main:5
。。。。

原理:

  1、JVM执行  MainThread类的main 方法时,知道OS开辟了一条main方法通向CPU的路径。

    这个路径叫做main线程,主线程。CPU通过这个路径可以执行main方法。

  2、JVM执行到  mt.start();时,开辟了一条通向CPU的新路径来 执行run方法。

  3、对于CPU而言,就有了两条执行的路径,CPU就有了选择权,我们控制不了CPU,

    两个线程,main线程和,新的 MyThread的新线程,一起抢夺CPU的执行权

    (执行时间),谁抢到谁执行。

      多次启动一个线程是非法的。特别是当线程已经结束执行后,不能再重新启动。

    java程序属于抢占式调度,优先级高的线程优先执行;同一个优先级,随机执行

线程的内存解析图:

二、创建多线程程序的第二种方式: 实现(implements) Runnable 类

  实现Runable接口的子类:MyRunbale
    //1.创建一个Runnable接口的实现类
public class MyRunnable implements Runnable {
//2.在实现类中重写Runnable接口的run方法,设置线程任务
@Override
public void run() {
for (int i = 0; i <20 ; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}

  主线程: MyThread

public class MainThread {
public static void main(String[] args) {
//3.创建一个Runnable接口的实现类对象
MyRunnable run = new MyRunnable();
//4.创建Thread类对象,构造方法中传递Runnable接口的实现类对象
//Thread t = new Thread(run);//打印线程名称
Thread t = new Thread(run);//打印HelloWorld
//5.调用Thread类中的start方法,开启新的线程执行run方法
t.start(); for (int i = 0; i <20 ; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}

结果:随机性打印

main-->0
Thread-0-->0
main-->1
Thread-0-->1
main-->2
Thread-0-->2
main-->3
Thread-0-->3
main-->4
Thread-0-->4
main-->5
Thread-0-->5
main-->6
Thread-0-->6
main-->7
Thread-0-->7

原理和内存和实现Thread相识。
实现Runnable接口创建多线程程序的好处:
1.避免了单继承的局限性
一个类只能继承一个类(一个人只能有一个亲爹),类继承了Thread类就不能继承其他的类
实现了Runnable接口,还可以继承其他的类,实现其他的接口
2.增强了程序的扩展性,降低了程序的耦合性(解耦)
实现Runnable接口的方式,把设置线程任务和开启新线程进行了分离(解耦)
实现类中,重写了run方法:用来设置线程任务
创建Thread类对象,调用start方法:用来开启新线程

三、匿名内部类实现线程的创建

 匿名内部类作用:简化代码
把子类继承父类,重写父类的方法,创建子类对象合一步完成
把实现类实现类接口,重写接口中的方法,创建实现类对象合成一步完成
匿名内部类的最终产物:子类/实现类对象,而这个类没有名字

实现代码:

/*
匿名内部类方式实现线程的创建 匿名:没有名字
内部类:写在其他类内部的类 格式:
new 父类/接口(){
重复父类/接口中的方法
};
*/
public class InnerClassThread {
public static void main(String[] args) {
//线程的父类是Thread
// new MyThread().start();
new Thread(){
//重写run方法,设置线程任务
@Override
public void run() {
for (int i = 0; i <20 ; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+"NOT_Copy");
}
}
}.start(); //线程的接口Runnable
//Runnable r = new RunnableImpl();//多态
Runnable r = new Runnable(){
//重写run方法,设置线程任务
@Override
public void run() {
for (int i = 0; i <20 ; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+"程序员");
}
}
};
new Thread(r).start(); //简化接口的方式
new Thread(new Runnable(){
//重写run方法,设置线程任务
@Override
public void run() {
for (int i = 0; i <20 ; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+"MWW");
}
}
}).start();
}
}

四、线程安全

  1、线程安全产生的原因:

  1、单线程不会出现线程安全问题

   2、多个线程,没有访问共享资源,也不会产生线程安全问题

   3、多个线程,且访问了共享资源,就会产生线程安全问题。

  代码示例:

  线程实现类:

/*
实现卖票案例
*/
public class RunnableImpl implements Runnable{
//定义一个多个线程共享的票源
private int ticket = 100;
//设置线程任务:卖票
@Override
public void run() {
//使用死循环,让卖票操作重复执行
while(true){
//先判断票是否存在
if(ticket>0){
try {
Thread.sleep(10); //提高安全问题出现的概率,让程序睡眠
} catch (InterruptedException e) {
e.printStackTrace();
}
//票存在,卖票 ticket--
System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
ticket--;
}
}
}
}

  MainThread类:

/*
模拟卖票案例
创建3个线程,同时开启,对共享的票进行出售
*/
public class MainThread {
public static void main(String[] args) {
//创建Runnable接口的实现类对象
RunnableImpl run = new RunnableImpl();
//创建Thread类对象,构造方法中传递Runnable接口的实现类对象
Thread t0 = new Thread(run);
Thread t1 = new Thread(run);
Thread t2 = new Thread(run);
//调用start方法开启多线程
t0.start();
t1.start();
t2.start();
}
}

结果:

Thread-1-->正在卖第100张票
Thread-0-->正在卖第99张票
Thread-2-->正在卖第98张票
Thread-1-->正在卖第97张票
Thread-0-->正在卖第96张票
Thread-2-->正在卖第95张票
Thread-1-->正在卖第94张票
Thread-0-->正在卖第94张票
Thread-2-->正在卖第92张票
Thread-1-->正在卖第91张票
Thread-0-->正在卖第91张票
Thread-2-->正在卖第89张票
Thread-0-->正在卖第88张票
Thread-1-->正在卖第88张票
Thread-2-->正在卖第86张票
Thread-0-->正在卖第85张票
Thread-1-->正在卖第85张票
Thread-2-->正在卖第83张票
Thread-1-->正在卖第82张票
Thread-0-->正在卖第82张票
Thread-2-->正在卖第80张票
Thread-0-->正在卖第79张票
Thread-1-->正在卖第79张票
Thread-2-->正在卖第77张票
Thread-1-->正在卖第76张票
Thread-0-->正在卖第76张票
Thread-2-->正在卖第74张票
Thread-1-->正在卖第73张票
Thread-0-->正在卖第73张票
Thread-2-->正在卖第71张票
Thread-1-->正在卖第70张票
Thread-0-->正在卖第70张票
Thread-2-->正在卖第68张票
Thread-0-->正在卖第67张票
Thread-1-->正在卖第67张票
Thread-2-->正在卖第65张票
Thread-0-->正在卖第64张票
Thread-1-->正在卖第64张票
Thread-2-->正在卖第62张票
Thread-1-->正在卖第61张票
Thread-0-->正在卖第61张票
Thread-2-->正在卖第59张票
Thread-0-->正在卖第58张票
Thread-1-->正在卖第58张票
Thread-2-->正在卖第56张票
Thread-1-->正在卖第55张票
Thread-0-->正在卖第55张票
Thread-2-->正在卖第53张票
Thread-1-->正在卖第52张票
Thread-0-->正在卖第52张票
Thread-2-->正在卖第50张票
Thread-0-->正在卖第49张票
Thread-1-->正在卖第49张票
Thread-2-->正在卖第47张票
Thread-0-->正在卖第46张票
Thread-1-->正在卖第46张票
Thread-2-->正在卖第44张票
Thread-1-->正在卖第43张票
Thread-0-->正在卖第42张票
Thread-2-->正在卖第41张票
Thread-2-->正在卖第40张票
Thread-1-->正在卖第40张票
Thread-0-->正在卖第40张票
Thread-1-->正在卖第37张票
Thread-2-->正在卖第37张票
Thread-0-->正在卖第37张票
Thread-0-->正在卖第34张票
Thread-1-->正在卖第34张票
Thread-2-->正在卖第34张票
Thread-1-->正在卖第31张票
Thread-2-->正在卖第31张票
Thread-0-->正在卖第31张票
Thread-1-->正在卖第28张票
Thread-0-->正在卖第28张票
Thread-2-->正在卖第28张票
Thread-2-->正在卖第25张票
Thread-1-->正在卖第25张票
Thread-0-->正在卖第25张票
Thread-1-->正在卖第22张票
Thread-0-->正在卖第22张票
Thread-2-->正在卖第22张票
Thread-1-->正在卖第19张票
Thread-0-->正在卖第19张票
Thread-2-->正在卖第19张票
Thread-0-->正在卖第16张票
Thread-1-->正在卖第16张票
Thread-2-->正在卖第16张票
Thread-1-->正在卖第13张票
Thread-0-->正在卖第13张票
Thread-2-->正在卖第13张票
Thread-0-->正在卖第10张票
Thread-1-->正在卖第10张票
Thread-2-->正在卖第10张票
Thread-0-->正在卖第7张票
Thread-1-->正在卖第7张票
Thread-2-->正在卖第7张票
Thread-2-->正在卖第4张票
Thread-1-->正在卖第4张票
Thread-0-->正在卖第4张票
Thread-0-->正在卖第1张票
Thread-1-->正在卖第0张票
Thread-2-->正在卖第-1张票

结果出现了卖重复的票,卖不存在的票。

2、线程安全问题的解决方案:

  1)、同步代码块:

/*
格式:
synchronized(锁对象){
可能会出现线程安全问题的代码(访问了共享数据的代码)
} 注意:
1.通过代码块中的锁对象,可以使用任意的对象
2.但是必须保证多个线程使用的锁对象是同一个
3.锁对象作用:
把同步代码块锁住,只让一个线程在同步代码块中执行
*/

  修改线程实现类:

/*
实现卖票案例
*/
public class RunnableImpl implements Runnable{
//定义一个多个线程共享的票源
private int ticket = 100;
//创建一个锁对象
Object object=new Object();
//设置线程任务:卖票
@Override
public void run() {
//使用死循环,让卖票操作重复执行
while(true){
//同步代码块
synchronized (object){
//先判断票是否存在
if(ticket>0){
try {
Thread.sleep(10); //提高安全问题出现的概率,让程序睡眠
} catch (InterruptedException e) {
e.printStackTrace();
}
//票存在,卖票 ticket--
System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
ticket--;
}
}
}
}
}

将可能出现线程安全问题的代码放到   synchronized (object){}  代码块中。

结果:

Thread-0-->正在卖第100张票
Thread-0-->正在卖第99张票
Thread-0-->正在卖第98张票
Thread-0-->正在卖第97张票
Thread-2-->正在卖第96张票
Thread-2-->正在卖第95张票
Thread-2-->正在卖第94张票
Thread-2-->正在卖第93张票
Thread-2-->正在卖第92张票
Thread-2-->正在卖第91张票
Thread-2-->正在卖第90张票
Thread-2-->正在卖第89张票
Thread-2-->正在卖第88张票
Thread-2-->正在卖第87张票
Thread-2-->正在卖第86张票
Thread-2-->正在卖第85张票
Thread-2-->正在卖第84张票
Thread-2-->正在卖第83张票
Thread-2-->正在卖第82张票
Thread-2-->正在卖第81张票
Thread-2-->正在卖第80张票
Thread-2-->正在卖第79张票
Thread-2-->正在卖第78张票
Thread-2-->正在卖第77张票
Thread-2-->正在卖第76张票
Thread-2-->正在卖第75张票
Thread-2-->正在卖第74张票
Thread-2-->正在卖第73张票
Thread-2-->正在卖第72张票
Thread-2-->正在卖第71张票
Thread-2-->正在卖第70张票
Thread-2-->正在卖第69张票
Thread-2-->正在卖第68张票
Thread-2-->正在卖第67张票
Thread-2-->正在卖第66张票
Thread-2-->正在卖第65张票
Thread-2-->正在卖第64张票
Thread-2-->正在卖第63张票
Thread-2-->正在卖第62张票
Thread-2-->正在卖第61张票
Thread-2-->正在卖第60张票
Thread-2-->正在卖第59张票
Thread-2-->正在卖第58张票
Thread-2-->正在卖第57张票
Thread-2-->正在卖第56张票
Thread-2-->正在卖第55张票
Thread-2-->正在卖第54张票
Thread-2-->正在卖第53张票
Thread-2-->正在卖第52张票
Thread-2-->正在卖第51张票
Thread-2-->正在卖第50张票
Thread-2-->正在卖第49张票
Thread-2-->正在卖第48张票
Thread-2-->正在卖第47张票
Thread-2-->正在卖第46张票
Thread-2-->正在卖第45张票
Thread-2-->正在卖第44张票
Thread-2-->正在卖第43张票
Thread-2-->正在卖第42张票
Thread-2-->正在卖第41张票
Thread-2-->正在卖第40张票
Thread-2-->正在卖第39张票
Thread-2-->正在卖第38张票
Thread-2-->正在卖第37张票
Thread-2-->正在卖第36张票
Thread-2-->正在卖第35张票
Thread-2-->正在卖第34张票
Thread-2-->正在卖第33张票
Thread-2-->正在卖第32张票
Thread-2-->正在卖第31张票
Thread-2-->正在卖第30张票
Thread-2-->正在卖第29张票
Thread-2-->正在卖第28张票
Thread-2-->正在卖第27张票
Thread-2-->正在卖第26张票
Thread-2-->正在卖第25张票
Thread-2-->正在卖第24张票
Thread-2-->正在卖第23张票
Thread-2-->正在卖第22张票
Thread-2-->正在卖第21张票
Thread-2-->正在卖第20张票
Thread-2-->正在卖第19张票
Thread-2-->正在卖第18张票
Thread-2-->正在卖第17张票
Thread-2-->正在卖第16张票
Thread-2-->正在卖第15张票
Thread-2-->正在卖第14张票
Thread-2-->正在卖第13张票
Thread-2-->正在卖第12张票
Thread-2-->正在卖第11张票
Thread-2-->正在卖第10张票
Thread-2-->正在卖第9张票
Thread-1-->正在卖第8张票
Thread-1-->正在卖第7张票
Thread-1-->正在卖第6张票
Thread-1-->正在卖第5张票
Thread-1-->正在卖第4张票
Thread-1-->正在卖第3张票
Thread-1-->正在卖第2张票
Thread-1-->正在卖第1张票

不再出现重复票,和不存在的票了。

  实现原理:使用一个锁对象,这个对象叫做同步锁,也叫作对象监视器。

  上述代码中三个线程抢占CPU执行权,

  t0 抢到了CPUd 执行权,执行run方法,遇到synchronized代码块,

  这时候 t0 会检查 synchronized 代码块是否有锁对象,

  发现有就会获取到锁对象进入到同步代码块执行。

  t1 抢到了CPU的执行权,执行  run 方法,遇到 synchronized 代码块

  这时 t1会检查synchronized代码块是否有锁对象

  发现没有 t1 就会进入到阻塞状态,会一直等到 t0 线程归还 锁对象

  一直到 t0 线程执行完同步代码,会把锁对象归还给同步代码块。

  t1 才能获取到 锁对象 进入到同步执行。

  总结:同步中的线程没有执行完毕不会释放锁,没有锁也进不进同步代码,

    这样就保证了只有一个线程在同步中执行共享数据,保证了安全,

    程序频繁的判断锁,获取锁,释放锁,效率会降低。

 

  2)、同步方法

  修改线程实现类:

/*
实现卖票案例
*/
public class RunnableImpl implements Runnable{
//定义一个多个线程共享的票源
private int ticket = 100;
//创建一个锁对象
Object object=new Object();
//设置线程任务:卖票
@Override
public void run() {
//使用死循环,让卖票操作重复执行
while(true){
payTicket();
}
}
/*
定义一个同步方法
同步方法也会把方法内部的代码锁住
只让一个线程执行
同步方法的对象是谁?
就是实现类对象 new RunnableImpl()
*/
public synchronized void payTicket(){
//先判断票是否存在
if(ticket>0){
try {
Thread.sleep(10); //提高安全问题出现的概率,让程序睡眠
} catch (InterruptedException e) {
e.printStackTrace();
}
//票存在,卖票 ticket--
System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
ticket--;
}
}
}

还可以用静态的同步代码:

package DemoThread;
/*
实现卖票案例
*/
public class RunnableImpl implements Runnable{
//定义一个多个线程共享的票源
private static int ticket = 100;
//创建一个锁对象
Object object=new Object();
//设置线程任务:卖票
@Override
public void run() {
//使用死循环,让卖票操作重复执行
while(true){
payTicketStatic();
}
}
/*
定义一个静态的同步方法
对象是谁?
就是实现类对象 本类的class属相 --> class文件对象(反射)
*/
public static synchronized void payTicketStatic(){
//先判断票是否存在
if(ticket>0){
try {
Thread.sleep(10); //提高安全问题出现的概率,让程序睡眠
} catch (InterruptedException e) {
e.printStackTrace();
}
//票存在,卖票 ticket--
System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
ticket--;
}
}
}

将可能出现线程安全问题的代码放到   synchronized 修饰的方法中。

结果:

Thread-0-->正在卖第100张票
Thread-2-->正在卖第99张票
Thread-2-->正在卖第98张票
Thread-2-->正在卖第97张票
Thread-2-->正在卖第96张票
Thread-2-->正在卖第95张票
Thread-2-->正在卖第94张票
Thread-2-->正在卖第93张票
Thread-2-->正在卖第92张票
Thread-2-->正在卖第91张票
Thread-2-->正在卖第90张票
Thread-2-->正在卖第89张票
Thread-2-->正在卖第88张票
Thread-2-->正在卖第87张票
Thread-2-->正在卖第86张票
Thread-2-->正在卖第85张票
Thread-2-->正在卖第84张票
Thread-2-->正在卖第83张票
Thread-2-->正在卖第82张票
Thread-2-->正在卖第81张票
Thread-2-->正在卖第80张票
Thread-2-->正在卖第79张票
Thread-2-->正在卖第78张票
Thread-2-->正在卖第77张票
Thread-2-->正在卖第76张票
Thread-2-->正在卖第75张票
Thread-2-->正在卖第74张票
Thread-2-->正在卖第73张票
Thread-2-->正在卖第72张票
Thread-2-->正在卖第71张票
Thread-2-->正在卖第70张票
Thread-2-->正在卖第69张票
Thread-2-->正在卖第68张票
Thread-2-->正在卖第67张票
Thread-2-->正在卖第66张票
Thread-2-->正在卖第65张票
Thread-2-->正在卖第64张票
Thread-2-->正在卖第63张票
Thread-2-->正在卖第62张票
Thread-2-->正在卖第61张票
Thread-2-->正在卖第60张票
Thread-2-->正在卖第59张票
Thread-2-->正在卖第58张票
Thread-2-->正在卖第57张票
Thread-2-->正在卖第56张票
Thread-2-->正在卖第55张票
Thread-2-->正在卖第54张票
Thread-2-->正在卖第53张票
Thread-2-->正在卖第52张票
Thread-2-->正在卖第51张票
Thread-2-->正在卖第50张票
Thread-2-->正在卖第49张票
Thread-2-->正在卖第48张票
Thread-2-->正在卖第47张票
Thread-2-->正在卖第46张票
Thread-2-->正在卖第45张票
Thread-2-->正在卖第44张票
Thread-2-->正在卖第43张票
Thread-2-->正在卖第42张票
Thread-2-->正在卖第41张票
Thread-2-->正在卖第40张票
Thread-2-->正在卖第39张票
Thread-2-->正在卖第38张票
Thread-2-->正在卖第37张票
Thread-2-->正在卖第36张票
Thread-2-->正在卖第35张票
Thread-2-->正在卖第34张票
Thread-2-->正在卖第33张票
Thread-2-->正在卖第32张票
Thread-2-->正在卖第31张票
Thread-2-->正在卖第30张票
Thread-2-->正在卖第29张票
Thread-2-->正在卖第28张票
Thread-2-->正在卖第27张票
Thread-2-->正在卖第26张票
Thread-2-->正在卖第25张票
Thread-2-->正在卖第24张票
Thread-2-->正在卖第23张票
Thread-2-->正在卖第22张票
Thread-2-->正在卖第21张票
Thread-2-->正在卖第20张票
Thread-2-->正在卖第19张票
Thread-2-->正在卖第18张票
Thread-2-->正在卖第17张票
Thread-2-->正在卖第16张票
Thread-2-->正在卖第15张票
Thread-2-->正在卖第14张票
Thread-2-->正在卖第13张票
Thread-2-->正在卖第12张票
Thread-2-->正在卖第11张票
Thread-2-->正在卖第10张票
Thread-2-->正在卖第9张票
Thread-2-->正在卖第8张票
Thread-2-->正在卖第7张票
Thread-2-->正在卖第6张票
Thread-2-->正在卖第5张票
Thread-2-->正在卖第4张票
Thread-2-->正在卖第3张票
Thread-2-->正在卖第2张票
Thread-2-->正在卖第1张票

同步方法和静态同步的方法的不同点在于,同步锁的对象不同。

同步方法     的锁对象就是 实现类对象  new RunnableImpl()
静态同步的方法  的锁对象就是 本类的 RunnableImpl.class。

  3)、锁机制

代码实现:

/*
卖票案例出现了线程安全问题
卖出了不存在的票和重复的票 解决线程安全问题的三种方案:使用Lock锁
java.util.concurrent.locks.Lock接口
Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。
Lock接口中的方法:
void lock()获取锁。
void unlock() 释放锁。
java.util.concurrent.locks.ReentrantLock implements Lock接口 使用步骤:
1.在成员位置创建一个ReentrantLock对象
2.在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁
3.在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁
*/
public class RunnableImpl implements Runnable{
//定义一个多个线程共享的票源
private int ticket = 100;
//1.在成员位置创建一个ReentrantLock对象
Lock lock1 = new ReentrantLock();
//设置线程任务:卖票
@Override
public void run() {
//使用死循环,让卖票操作重复执行
while(true){
lock1.lock(); //2.在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁
if(ticket>0){
try {
Thread.sleep(10); //提高安全问题出现的概率,让程序睡眠
//票存在,卖票 ticket--
System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
ticket--;
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock1.unlock();//3.在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁
//放在finally{} 中 无论程序是否异常,都会把锁释放。
}
}
}
}
}

结果同上面的两种方式相同。

五、线程的状态

线程状态  导致状态发生条件
NEW(新建)  线程刚被创建,但是并未启动。还没调用start方法。
Runnable(可运行)
线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操
作系统处理器。
Blocked(锁阻塞)
当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状
态;当该线程持有锁时,该线程将变成Runnable状态。
Waiting(无限等待)
一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个
状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。
TimedWaiting(计时等待)
同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态
将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep 、Object.wait。
Teminated(被终止)
因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。

  1、Timed Waiting 线程状态图:

  sleep方法的使用还是很简单的。我们需要记住下面几点:
    1. 进入 TIMED_WAITING 状态的一种常见情形是调用的 sleep 方法,
      单独的线程也可以调用,不一定非要有协作关系。
    2. 为了让其他线程有机会执行,可以将Thread.sleep()的调用放线程run()之内。
      这样才能保证该线程执行过程中会睡眠
    3. sleep与锁无关,线程睡眠到期自动苏醒,并返回到Runnable(可运行)状态。
 
    注:sleep()中指定的时间是线程不会运行的最短时间。
    因此,sleep()方法不能保证该线程睡眠到期后就开始立刻执行。

  2、Blocked(锁阻塞)

  上面已经讲过了同步机制,那么这个状态也就非常好理解了,比如线程A与线程B代码中使用

  同一把锁,如果线程A获取到锁,线程A进入到Runnable状态,那么线程B进入到Blocked锁阻塞状态。

  这里是由Runnable状态进入Blocked状态,除此之外Waiting(无限等待)以及

  Time Waiting(计时等待)也会在某种情况下进入到阻塞状态

 3、Waiting(无限等待)

    1、等待与唤醒案例:

/*
等待唤醒案例:线程之间的通信
创建一个顾客线程(消费者):告知老板要的包子的种类和数量,调用wait方法,放弃cpu的执行,进入到WAITING状态(无限等待)
创建一个老板线程(生产者):花了5秒做包子,做好包子之后,调用notify方法,唤醒顾客吃包子 注意:
顾客和老板线程必须使用同步代码块包裹起来,保证等待和唤醒只能有一个在执行
同步使用的锁对象必须保证唯一
只有锁对象才能调用wait和notify方法 Obejct类中的方法
void wait()
在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。
void notify()
唤醒在此对象监视器上等待的单个线程。
会继续执行wait方法之后的代码
*/
public class MainThread {
public static void main(String[] args) {
//创建锁对象,保证唯一
Object obj = new Object();
// 创建一个顾客线程(消费者)
new Thread(){
@Override
public void run() {
//一直等着买包子
while(true){
//保证等待和唤醒的线程只能有一个执行,需要使用同步技术
synchronized (obj){
System.out.println("告知老板要的包子的种类和数量");
//调用wait方法,放弃cpu的执行,进入到WAITING状态(无限等待)
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
//唤醒之后执行的代码
System.out.println("包子已经做好了,开吃!");
System.out.println("---------------------------------------");
}
}
}
}.start(); //创建一个老板线程(生产者)
new Thread(){
@Override
public void run() {
//一直做包子
while (true){
//花了5秒做包子
try {
Thread.sleep(5000);//花5秒钟做包子
} catch (InterruptedException e) {
e.printStackTrace();
} //保证等待和唤醒的线程只能有一个执行,需要使用同步技术
synchronized (obj){
System.out.println("老板5秒钟之后做好包子,告知顾客,可以吃包子了");
//做好包子之后,调用notify方法,唤醒顾客吃包子
obj.notify();
}
}
}
}.start();
}
}

结果:

告知老板要的包子的种类和数量
老板5秒钟之后做好包子,告知顾客,可以吃包子了
包子已经做好了,开吃!
---------------------------------------
告知老板要的包子的种类和数量
老板5秒钟之后做好包子,告知顾客,可以吃包子了
包子已经做好了,开吃!
---------------------------------------
告知老板要的包子的种类和数量

  2、等待与唤醒案例2:wait(long m)和notifyAll() 方法

代码实现:

/*
进入到TimeWaiting(计时等待)有两种方式
1.使用sleep(long m)方法,在毫秒值结束之后,线程睡醒进入到Runnable/Blocked状态
2.使用wait(long m)方法,wait方法如果在毫秒值结束之后,还没有被notify唤醒,就会自动醒来,线程睡醒进入到Runnable/Blocked状态 唤醒的方法:
void notify() 唤醒在此对象监视器上等待的单个线程。
void notifyAll() 唤醒在此对象监视器上等待的所有线程。
*/
public class MainThread {
public static void main(String[] args) {
//创建锁对象,保证唯一
Object obj = new Object();
// 创建一个顾客线程(消费者)
new Thread(){
@Override
public void run() {
//一直等着买包子
while(true){
//保证等待和唤醒的线程只能有一个执行,需要使用同步技术
synchronized (obj){
System.out.println("顾客1告知老板要的包子的种类和数量");
//调用wait方法,放弃cpu的执行,进入到WAITING状态(无限等待)
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
//唤醒之后执行的代码
System.out.println("包子已经做好了,顾客1开吃!");
System.out.println("---------------------------------------");
}
}
}
}.start(); // 创建一个顾客线程(消费者)
new Thread(){
@Override
public void run() {
//一直等着买包子
while(true){
//保证等待和唤醒的线程只能有一个执行,需要使用同步技术
synchronized (obj){
System.out.println("顾客2告知老板要的包子的种类和数量");
//调用wait方法,放弃cpu的执行,进入到WAITING状态(无限等待)
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
//唤醒之后执行的代码
System.out.println("包子已经做好了,顾客2开吃!");
System.out.println("---------------------------------------");
}
}
}
}.start(); //创建一个老板线程(生产者)
new Thread(){
@Override
public void run() {
//一直做包子
while (true){
//花了5秒做包子
try {
Thread.sleep(5000);//花5秒钟做包子
} catch (InterruptedException e) {
e.printStackTrace();
} //保证等待和唤醒的线程只能有一个执行,需要使用同步技术
synchronized (obj){
System.out.println("老板5秒钟之后做好包子,告知顾客,可以吃包子了");
//做好包子之后,调用notify方法,唤醒顾客吃包子
//obj.notify();//如果有多个等待线程,随机唤醒一个
obj.notifyAll();//唤醒所有等待的线程
}
}
}
}.start();
}
}

结果:

顾客1告知老板要的包子的种类和数量
顾客2告知老板要的包子的种类和数量
老板5秒钟之后做好包子,告知顾客,可以吃包子了
包子已经做好了,顾客2开吃!
---------------------------------------
顾客2告知老板要的包子的种类和数量
包子已经做好了,顾客1开吃!
---------------------------------------
顾客1告知老板要的包子的种类和数量
老板5秒钟之后做好包子,告知顾客,可以吃包子了
包子已经做好了,顾客1开吃!
---------------------------------------
顾客1告知老板要的包子的种类和数量
包子已经做好了,顾客2开吃!
---------------------------------------

等待与唤醒机制:又名 线程之间的通讯。

  等待唤醒机制就是用于解决线程间通信的问题的,使用到的3个方法的含义如下:
  1. wait:线程不再活动,不再参与调度,进入 wait set 中,因此不会浪费 CPU 资源,也不会去竞争锁了,
    这时的线程状态即是 WAITING。它还要等着别的线程执行一个特别的动作,也即是“通知(notify)”在这个对象
    上等待的线程从wait set 中释放出来,重新进入到调度队列(ready queue)中
  2. notify:则选取所通知对象的 wait set 中的一个线程释放;例如,餐馆有空位置后,等候就餐最久的顾客最先入座。
  3. notifyAll:则释放所通知对象的 wait set 上的全部线程。
 
  注:哪怕只通知了一个等待的线程,被通知线程也不能立即恢复执行,因为它当初中断的地方是在同步块内,而
    此刻它已经不持有锁,所以它需要再次尝试去获取锁(很可能面临其它线程的竞争),成功后才能在当初调
    用 wait 方法之后的地方恢复执行。
 
  总结如下:
  如果能获取锁,线程就从 WAITING 状态变成 RUNNABLE 状态;
  否则,从 wait set 出来,又进入 entry set,线程就从 WAITING 状态又变成 BLOCKED 状态

如下所示:

调用wait和notify方法需要注意的细节
  1. wait方法与notify方法必须要由同一个锁对象调用。因为:对应的锁对象可以通过notify唤醒使用同一个锁对
    象调用的wait方法后的线程。
  2. wait方法与notify方法是属于Object类的方法的。因为:锁对象可以是任意对象,而任意对象的所属类都是继
    承了Object类的。
  3. wait方法与notify方法必须要在同步代码块或者是同步函数中使用。因为:必须要通过锁对象调用这2个方法。 

  3、生产者和消费者问题

  等待唤醒机制其实就是经典的“生产者与消费者”的问题。

  重新整理代码:

  BaoZi类:

/*
资源类:包子类
设置包子的属性


包子的状态: 有 true,没有 false
*/
public class BaoZi {
private String pier ;
private String xianer ;
private boolean flag = false ;//包子资源 是否存在 包子资源状态 public String getPier() {
return pier;
} public void setPier(String pier) {
this.pier = pier;
} public String getXianer() {
return xianer;
} public void setXianer(String xianer) {
this.xianer = xianer;
} public boolean isFlag() {
return flag;
} public void setFlag(boolean flag) {
this.flag = flag;
}
}

  BaoZiPu类:

/*
生产者(包子铺)类:是一个线程类,可以继承Thread
设置线程任务(run):生产包子
对包子的状态进行判断
true:有包子
包子铺调用wait方法进入等待状态
false:没有包子
包子铺生产包子
增加一些趣味性:交替生产两种包子
有两种状态(i%2==0)
包子铺生产好了包子
修改包子的状态为true有
唤醒吃货线程,让吃货线程吃包子 注意:
包子铺线程和包子线程关系-->通信(互斥)
必须同时同步技术保证两个线程只能有一个在执行
锁对象必须保证唯一,可以使用包子对象作为锁对象
包子铺类和吃货的类就需要把包子对象作为参数传递进来
1.需要在成员位置创建一个包子变量
2.使用带参数构造方法,为这个包子变量赋值
*/
public class BaoZiPu extends Thread {
//1.需要在成员位置创建一个包子变量
private BaoZi bz; //2.使用带参数构造方法,为这个包子变量赋值
public BaoZiPu(BaoZi bz) {
this.bz = bz;
} //设置线程任务(run):生产包子
@Override
public void run() {
//定义一个变量
int count = 0;
//让包子铺一直生产包子
while(true){
//必须同时同步技术保证两个线程只能有一个在执行
synchronized (bz){
//对包子的状态进行判断
if(bz.isFlag()==true){
//包子铺调用wait方法进入等待状态
try {
bz.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//被唤醒之后执行,包子铺生产包子
//增加一些趣味性:交替生产两种包子
if(count%2==0){
//生产 薄皮三鲜馅包子
bz.setPier("薄皮");
bz.setXianer("三鲜馅");
}else{
//生产 冰皮 牛肉大葱陷
bz.setPier("冰皮");
bz.setXianer("牛肉大葱陷");
}
count++;
System.out.println("包子铺正在生产:"+bz.getPier()+bz.getXianer()+"包子");
//生产包子需要3秒钟
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//包子铺生产好了包子
//修改包子的状态为true有
bz.setFlag(true);
//唤醒吃货线程,让吃货线程吃包子
bz.notify();
System.out.println("包子铺已经生产好了:"+bz.getPier()+bz.getXianer()+"包子,吃货可以开始吃了");
}
}
}
}

  ChiHuo类:

/*
消费者(吃货)类:是一个线程类,可以继承Thread
设置线程任务(run):吃包子
对包子的状态进行判断
false:没有包子
吃货调用wait方法进入等待状态
true:有包子
吃货吃包子
吃货吃完包子
修改包子的状态为false没有
吃货唤醒包子铺线程,生产包子
*/
public class ChiHuo extends Thread{
//1.需要在成员位置创建一个包子变量
private BaoZi bz; //2.使用带参数构造方法,为这个包子变量赋值
public ChiHuo(BaoZi bz) {
this.bz = bz;
}
//设置线程任务(run):吃包子
@Override
public void run() {
//使用死循环,让吃货一直吃包子
while (true){
//必须同时同步技术保证两个线程只能有一个在执行
synchronized (bz){
//对包子的状态进行判断
if(bz.isFlag()==false){
//吃货调用wait方法进入等待状态
try {
bz.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//被唤醒之后执行的代码,吃包子
System.out.println("吃货正在吃:"+bz.getPier()+bz.getXianer()+"的包子");
//吃货吃完包子
//修改包子的状态为false没有
bz.setFlag(false);
//吃货唤醒包子铺线程,生产包子
bz.notify();
System.out.println("吃货已经把:"+bz.getPier()+bz.getXianer()+"的包子吃完了,包子铺开始生产包子");
System.out.println("----------------------------------------------------");
}
}
}
}

  测试类:

/*
测试类:
包含main方法,程序执行的入口,启动程序
创建包子对象;
创建包子铺线程,开启,生产包子;
创建吃货线程,开启,吃包子;
*/
public class Demo {
public static void main(String[] args) {
//创建包子对象;
BaoZi bz =new BaoZi();
//创建包子铺线程,开启,生产包子;
new BaoZiPu(bz).start();
//创建吃货线程,开启,吃包子;
new ChiHuo(bz).start();
}
}

结果:

包子铺正在生产:薄皮三鲜馅包子
包子铺已经生产好了:薄皮三鲜馅包子,吃货可以开始吃了
吃货正在吃:薄皮三鲜馅的包子
吃货已经把:薄皮三鲜馅的包子吃完了,包子铺开始生产包子
----------------------------------------------------
包子铺正在生产:冰皮牛肉大葱陷包子
包子铺已经生产好了:冰皮牛肉大葱陷包子,吃货可以开始吃了
吃货正在吃:冰皮牛肉大葱陷的包子
吃货已经把:冰皮牛肉大葱陷的包子吃完了,包子铺开始生产包子
----------------------------------------------------
包子铺正在生产:薄皮三鲜馅包子
包子铺已经生产好了:薄皮三鲜馅包子,吃货可以开始吃了
吃货正在吃:薄皮三鲜馅的包子
吃货已经把:薄皮三鲜馅的包子吃完了,包子铺开始生产包子
----------------------------------------------------
包子铺正在生产:冰皮牛肉大葱陷包子

等待与唤醒机制:又名 线程之间的通讯。

原理说明:

  通讯:对包子的状态进行判断

  没有包子-->吃货线程 唤醒包子铺线程-->吃货等待-->包子铺线程做包子-->做好包子-->修改包子的状态为有包子

  有包子-->包子铺线程唤醒 吃货线程-->包子铺线程等待-->吃货线程吃包子-->吃完包子-->修改包子的状态为没有包子

  。。。

六、线程池

  为什么使用线程:

    当并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,

    这样频繁创建线程就会大大的降低系统的效率,因为频繁创建线程和销毁线程需要时间。

  1、线程池的概念

    线程池:其实就是一个容纳多个线程的容器,其中线程可以反复使用,省去了频繁创建线程对象的操作,

    无需反复创建线程池而消耗了过多的资源。

    图解:

合理利用线程池能够带来三个好处:

  1、降低资源消耗。减少了创建和销毁线程的次数,每个工作线程都可以被重复使用,可执行多个任务。

  2、提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行

   3、提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内

     存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)

 2、线程池的使用

/*
2.创建一个类,实现Runnable接口,重写run方法,设置线程任务
*/
public class RunnableImpl implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"创建了一个新的线程执行");
}
}

  测试类;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; /*
线程池:JDK1.5之后提供的
java.util.concurrent.Executors:线程池的工厂类,用来生成线程池
Executors类中的静态方法:
static ExecutorService newFixedThreadPool(int nThreads) 创建一个可重用固定线程数的线程池
参数:
int nThreads:创建线程池中包含的线程数量
返回值:
ExecutorService接口,返回的是ExecutorService接口的实现类对象,
       我们可以使用ExecutorService接口接收(面向接口编程)
  java.util.concurrent.ExecutorService:线程池接口
用来从线程池中获取线程,调用start方法,执行线程任务
submit(Runnable task) 提交一个 Runnable 任务用于执行
关闭/销毁线程池的方法
void shutdown()
线程池的使用步骤:
1.使用线程池的工厂类Executors里边提供的静态方法newFixedThreadPool生产一个指定线程数量的线程池
2.创建一个类,实现Runnable接口,重写run方法,设置线程任务
3.调用ExecutorService中的方法submit,传递线程任务(实现类),开启线程,执行run方法
4.调用ExecutorService中的方法shutdown销毁线程池(不建议执行)
*/
public class MainThread {
public static void main(String[] args) {
//1.使用线程池的工厂类Executors里边提供的静态方法newFixedThreadPool生产一个指定线程数量的线程池
ExecutorService es = Executors.newFixedThreadPool(2);
//3.调用ExecutorService中的方法submit,传递线程任务(实现类),开启线程,执行run方法
es.submit(new RunnableImpl());//pool-1-thread-1创建了一个新的线程执行
//线程池会一直开启,使用完了线程,会自动把线程归还给线程池,线程可以继续使用
es.submit(new RunnableImpl());//pool-1-thread-1创建了一个新的线程执行
es.submit(new RunnableImpl());//pool-1-thread-2创建了一个新的线程执行 //4.调用ExecutorService中的方法shutdown销毁线程池(不建议执行)
es.shutdown();
es.submit(new RunnableImpl());//抛异常,线程池都没有了,就不能获取线程了
}
}

结果:

pool-1-thread-1创建了一个新的线程执行
pool-1-thread-2创建了一个新的线程执行
pool-1-thread-1创建了一个新的线程执行
pool-1-thread-2创建了一个新的线程执行
pool-1-thread-2创建了一个新的线程执行

  因为线程使用完了,会自动把线程归还给线程池,线程可以继续使用,所以我只在线程池中设置了两个线程,

  却可以反复使用,执行多次任务。

注:多线程执行时为什么调用的是start()方法而不是run()方法?
    如果调用代码thread.run()就不是异步执行了,而是同步,那么此线程对象就不会交给“线程规划器”来进行处理。而是由main主线程来调用run()方法,也就是说必须要等到run()方法中的代码执行完成后才可以执行后面的代码。
  start()用来启动一个线程,当调用start()方法时,系统才会开启一个线程,通过Thead类中start()方法来启动的线程处于就绪状态(可运行状态),此时并没有运行,一旦得到CPU时间片,就自动开始执行run()方法。 

待续。。。。

Java基础之 多线程的更多相关文章

  1. Java基础技术多线程与并发面试【笔记】

    Java基础技术多线程与并发 什么是线程死锁? ​死锁是指两个或两个以上的进程(线程)在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去,我们就可以称 ...

  2. Java基础之多线程篇(线程创建与终止、互斥、通信、本地变量)

    线程创建与终止 线程创建 Thread类与Runnable接口的关系 public interface Runnable { public abstract void run(); } public ...

  3. 关于java基础、多线程、JavaWeb基础、数据库、SSM、Springboot技术汇总

    作者 : Stanley 罗昊 本人自行总结,纯手打,有疑问请在评论区留言 [转载请注明出处和署名,谢谢!] 一.java基础 1.多态有哪些体现形式? 重写.重载 2. Overriding的是什么 ...

  4. Java基础之多线程框架

    一.进程与线程的区别 1.定义: 进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位. 线程是进程的一个实体,是CPU调度和分派的基本单位,它是比 ...

  5. Java基础之多线程详细分析

    在了解多线程之前,先来了解一下进程与线程之间的关系. 进程和线程: 进程是指在系统中正在执行的一个程序,每个进程之间是独立的. 线程是进程的一个基本执行单元.一个进程要想执行任务,必须得有线程(每1个 ...

  6. java基础知识 多线程

    package org.base.practise9; import org.junit.Test; import java.awt.event.WindowAdapter; import java. ...

  7. Java基础之多线程

    1.进程和线程: 进程:正在进行的程序.每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元. 线程:进程内部的一条执行路径或者一个控制单元. 两者的区别: 一个进程至少有一个线程 ...

  8. 黑马程序员——【Java基础】——多线程

    ---------- android培训.java培训.期待与您交流! ---------- 一.概述 (一)进程 正在执行中的程序,每一个进程执行都有一个执行顺序.该顺序是一个执行路径,或者叫一个控 ...

  9. 黑马程序员——JAVA基础之多线程的线程间通讯等

    ------- android培训.java培训.期待与您交流! ---------- 线程间通讯: 其实就是多个线程在操作同一个资源,但是动作不同. wait(); 在其他线程调用此对象的notif ...

  10. 黑马程序员——JAVA基础之多线程的安全问题

    ------- android培训.java培训.期待与您交流! ---------- 导致多线程出现问题的一个特殊的状态:就绪.具备了执行资格,但是还没有获取资源. 导致安全问题的出现的原因: 1. ...

随机推荐

  1. hdu 3549 网络流最大流 Ford-Fulkerson

    Ford-Fulkerson方法依赖于三种重要思想,这三个思想就是:残留网络,增广路径和割. Ford-Fulkerson方法是一种迭代的方法.开始时,对所有的u,v∈V有f(u,v)=0,即初始状态 ...

  2. Python之网路编程之进程池及回调函数

    一.数据共享 1.进程间的通信应该尽量避免共享数据的方式 2.进程间的数据是独立的,可以借助队列或管道实现通信,二者都是基于消息传递的. 虽然进程间数据独立,但可以用过Manager实现数据共享,事实 ...

  3. 【NOIP2016提高A组五校联考4】ksum

    题目 分析 发现,当子段[l,r]被取了出来,那么[l-1,r].[l,r+1]一定也被取了出来. 那么,首先将[1,n]放入大顶堆,每次将堆顶的子段[l,r]取出来,因为它是堆顶,所以一定是最大的子 ...

  4. 【mysql】全文索引match多列报错

    表结构如下: CREATE TABLE `T` ( .... FULLTEXT KEY `title_fc` (`titleindex`), FULLTEXT KEY `shortname_fc` ( ...

  5. JavaScript 中的 for 循环---------------引用

    在ECMAScript5(简称 ES5)中,有三种 for 循环,分别是: 简单for循环 for-in forEach 下面先来看看大家最常见的一种写法: 当数组长度在循环过程中不会改变时,我们应将 ...

  6. 大数乘法(A * B Problem Plus)问题

    大数乘法问题一般可以通过将大数转换为数组来解决. 解题思路 第1步 第2步 第3步 第4步 样例输入1 56 744 样例输出1 800 样例输入2 -10 678 样例输出2 -6780 样例输入3 ...

  7. BZOJ 3143 Luogu P3232 [HNOI2013]游走 (DP、高斯消元)

    题目链接: (bzoj) https://www.lydsy.com/JudgeOnline/problem.php?id=3143 (luogu) https://www.luogu.org/pro ...

  8. sublime text3安装格式化代码插件

    1.代码提示插件:sublimeCodeIntel a)在Sublime Text 3中,按下Ctrl+Shift+P调出命令面板;b)输入install 调出 Install Package 选项并 ...

  9. nowcoder---常州大学新生寒假训练会试----F 大佬的生日礼包(二分)

    链接:https://www.nowcoder.net/acm/contest/78/F 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 32768K,其他语言65536K 64bit ...

  10. matplotlib画图——条形图

    一.单条 import numpy as np import matplotlib.pyplot as plt N = 5 y1 = [20, 10, 30, 25, 15] y2 = [15, 14 ...