多线程学习笔记

进程是一个正在执行的程序
每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元。
Java VM 启动的时候会有一个进程java.exe
一个进程中至少有一个线程负责Java程序的运行,而且这个线程运行的代码存放在main方法中,该线程称之为主线程
扩展:其实更细节说明虚拟机,虚拟机不止启动一个线程,还有负责垃圾回收的线程。


第一种创建线程的步骤:
1.创建类继承Thread类或者实现Runable接口
2.复写Run方法
3.调用线程的start方法
该方法有两个作用:启动线程 ,调用run方法
示例如下:假设有4个售票窗口同时进行售票,每一个售票窗口可以理解为一个线程,四个窗口同时售票
也就是四个线程同时启动,而假定票数是10张,这样....每个窗口也就是线程都在运行时产生四个对象,会
有40张票,即每个对象产生10张票,一个线程,总共40张票4个线程,为了解决这个问题,可以考虑将
Ticket类中的num设置为static使四个对象共享这10张票。

Ticket类中的num设置为static使四个对象共享这10张票。
//Ticke类示例代码如下:
public class Ticket extends Thread{//实现线程需继承Thread类
private static int ticket_num=10;//静态共享数据假定为10张票
@Override
public void run() {//复写run方法
while(ticket_num>0)
{
if(ticket_num>0)
{
System.out.println(Thread.currentThread().getName()+"售票"+--ticket_num);
//Thread为类名,currentThread()为静态方法,可以由类名调用,作用是返回当前线程对象,getName(),拿到当前线程的名字
}
}
}
}
//TestMain示例代码如下:
public class TestMain {
public static void main(String[] args) {
Ticket t1=new Ticket();//创建四个Thread类的子对象
Ticket t2=new Ticket();
Ticket t3=new Ticket();
Ticket t4=new Ticket();
t1.start();t2.start();t3.start();t4.start();//启动这四个对象的线程
}
}

输出如下:Thread-1售票9
Thread-1售票6
Thread-1售票5
Thread-1售票4
Thread-1售票3
Thread-1售票2
Thread-1售票1
Thread-1售票0
Thread-0售票7
Thread-3售票8
--------------------------------可以观察到四个线程共享tiket_num这个数据,但是静态的对象属性生命周期过长,而且需要建立四个对象 所以可以考虑用实现Runnable接口的
方式来实现。


为什么要覆盖Run方法?
Thread类用于描写线程
该类就定义了一个功能,用于存储线程要运行的代码。该存储功能就是run方法
也就是说Thread类中的Run方法是用于存储线程要运行的代码

star方法和run方法的区别:
start--开启线程并执行该线程的run方法
run--仅仅是对象调用方法。而线程创建了,并没有执行 也就是只执行run方法内的代码 但是没有多线程


static Thread currentThread();获取当前线程对象
getName();获取线程名称
设置线程名称:setName或者构造函数
注意:每一个变量在线程当中都有独立的一份


创建线程的第二种方式:实现Runnable接口
步骤:
1.定义类实现Runable接口
2.覆盖Runable接口中的run方法
将线程要运行的代码放在该run方法中
3.通过Thread类建立线程对象
4.将Runable接口的子类对象作为实际参数传递给Thread类构造函数
为什么要将Runnable接口的子类对象传递给Thread的构造函数
因为,自定义的run方法所属的对象是实现Runnable接口的子类对象
所以要让线程去执行指定对象的run方法。就必须明确该run方法所属的对象
5.调用Thread类的start方法开启线程并调用Runable接口子类的run方法

售票示例如下:

public class Ticket implements Runnable{
private String name;//售票窗口名字
private int num;//票数
Ticket(String name,int num)
{
this.name=name;this.num=num;
}
public void run() {//复写run方法
while(this.num>0)
{
System.out.println(Thread.currentThread().getName()+"售票"+this.num--);
}
}
}
//TestMain程序代码如下:
public class TestMain {
public static void main(String[] args) {
Ticket t1=new Ticket("售票窗口",10);//创造实现Runnable接口的子类对象
new Thread(t1).start();//开启3个线程
new Thread(t1).start();
new Thread(t1).start();
}
}


输出如下:Thread-0售票10
Thread-2售票8
Thread-1售票9
Thread-1售票5
Thread-1售票4
Thread-1售票3
Thread-1售票2
Thread-1售票1
Thread-0售票6
Thread-2售票7


实现方式和继承方式有什么区别?
实现方式的好处:避免了单继承的局限性。在定义线程时,建议使用实现方式。
继承Thread:线程代码存放在Thread子类run方法中。
实现Runnable,线程代码存放在接口子类的run方法中。

多线程的安全隐患:(需要多加注意)
当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没
有执行完,另一个线程参与进来执行。导致了共享数据的错误。
解决办法:对于多条操作共享数据的语句,只能让一个线程都执行完,其他的线程才能
参与执行。
Java对于多线程的安全问题提供了专业的解决方案,就是同步代码块。
synchronized(对象){
需要被同步的代码
}

如何找到问题 找到该同步哪些代码块?
1.明确哪些代码是需要多线程运行代码。
2.明确共享数据。
3.明确多线程运行代码中的哪些语句是操作共享数据的。


同步函数---同步函数用的是哪一个锁?
---同步函数用的锁就是this,函数需要被对象调用,那么函数都有一个所属的对象的引用。就是this.
所以同步函数使用的锁是this.
验证示例:通过以下的程序来验证。需求为 --通过两个线程来买票一个线程在同步代码块中,另一
个线程在同步函数中。都在执行买票动作。
代码如下:

/**
* 售票类 有两个线程 同步代码块和同步函数 注意同步函数的锁为this而同步代码块的锁要达到
* 安全的目的则必须与同步函数的锁相同 也就是this
* @author bing
*
*/
public class Ticket implements Runnable{
private static int tickt=10;
Object obj = new Object();
boolean flag=true;
public void run()//同步代码块
{
if(flag)
{
while(true)
{
//synchronized (obj)//注意这里的锁是obj,这样与同步函数用的不是一个锁,所以会产生数据异常,比如售票时卖到0号票..
//synchronized (this)//这里用this锁,与同步函数用同一个锁
{
if(tickt>0)
{
try{Thread.sleep(10);}catch(Exception e){}
System.out.println("同步代码块进程---"+Thread.currentThread().getName()
+"-----sale:"+tickt--);
}
}

}
}else
{
while(true)
{
show();
}
}
}
public static synchronized void show()//同步函数
{
if(tickt>0)
{
try{Thread.sleep(10);}catch(Exception e){}
System.out.println("同步函数进程---"+Thread.currentThread().getName()
+"-----sale:"+tickt--);
}
}
}
//测试程序代码如下:
/**
* 验证线程锁,同步代码块和同步函数的控制通过flag旗帜来控制
* 注意主线程需要sleep一段时间
* @author bing
*
*/
public class TestMain {
public static void main(String[] args) throws InterruptedException {
Ticket aTicket=new Ticket();
Thread t1=new Thread(aTicket);
Thread t2=new Thread(aTicket);
t1.start();//线程t1开始启动,启动的时间短,非常有可能被main主线程抢走执行权,所以让主线程休息一会
Thread.currentThread().sleep(10);
aTicket.flag=false;System.out.println("-------------------------------------");
t2.start();
}
}

输出结果如下:
-------------------------------------
同步代码块进程---Thread-0-----sale:10
同步函数进程---Thread-1-----sale:8
同步代码块进程---Thread-0-----sale:9
同步代码块进程---Thread-0-----sale:7
同步函数进程---Thread-1-----sale:7
同步函数进程---Thread-1-----sale:6
同步代码块进程---Thread-0-----sale:5
同步代码块进程---Thread-0-----sale:4
同步函数进程---Thread-1-----sale:3
同步函数进程---Thread-1-----sale:2
同步代码块进程---Thread-0-----sale:1------------可以观察输出有序且线程Thread1和Thread0交替执行

那么上面的例子验证了同步函数的锁是this 那么静态同步函数的锁是什么呢?
--首先不是this,因为在static 中没有this.静态进内存是,内存中没有本类对象,但是一定有该类对应
的字节码文件对象。--->也就是类名.class 该对象类型是Class 即在静态函数中同步锁为类名.class

示例代码如下:

package DemoSynchronized_Static;
/**
* 售票类 有两个线程 同步代码块和同步函数 注意同步函数的锁为this而同步代码块的锁要达到
* 安全的目的则必须与同步函数的锁相同 也就是this
* @author bing
*
*/
public class Ticket implements Runnable{
private static int tickt=10;
Object obj = new Object();
boolean flag=true;
public void run()//同步代码块
{
if(flag)
{
while(true)
{
//synchronized (obj)//注意这里的锁是obj,这样与同步函数用的不是一个锁,所以会产生数据异常,比如售票时卖到0号票..
synchronized (Ticket.class)//这里用的是所在类的字节码文件对象,即类名.calss
{
if(tickt>0)
{
try{Thread.sleep(100);}catch(Exception e){}
System.out.println("同步代码块进程---"+Thread.currentThread().getName()
+"-----sale:"+tickt--);
}
}

}
}else
{
while(true)
{
show();//调用同步函数
}
}
}
public static synchronized void show()//同步函数
{
if(tickt>0)
{
try{Thread.sleep(10);}catch(Exception e){}
System.out.println("同步函数进程---"+Thread.currentThread().getName()
+"-----sale:"+tickt--);
}
}
}

//测试程序代码如下:

/**
* 验证线程锁,同步代码块和同步函数的控制通过flag旗帜来控制
* 注意主线程需要sleep一段时间
* @author bing
*
*/
public class TestMain {
public static void main(String[] args) throws InterruptedException {
Ticket aTicket=new Ticket();
Thread t1=new Thread(aTicket);
Thread t2=new Thread(aTicket);
t1.start();//线程t1开始启动,启动的时间短,非常有可能被main主线程抢走执行权,所以让主线程休息一会
Thread.currentThread().sleep(100);
aTicket.flag=false;System.out.println("-------------------------------------");
t2.start();
System.out.println("+++++++++++++++++++");
}
}


输出如下:
-------------------------------------
同步代码块进程---Thread-0-----sale:10
+++++++++++++++++++
同步代码块进程---Thread-0-----sale:9
同步代码块进程---Thread-0-----sale:8
同步函数进程---Thread-1-----sale:7
同步函数进程---Thread-1-----sale:6
同步函数进程---Thread-1-----sale:5
同步函数进程---Thread-1-----sale:4
同步函数进程---Thread-1-----sale:3
同步函数进程---Thread-1-----sale:2
同步函数进程---Thread-1-----sale:1
----------------------------------------小结:静态的同步方法:使用的锁是该方法所在类的字节码
文件对象。类名.class

多线程的单例设计模式
1懒汉式

package Single_Design_Pattern_Multithreading;
/**
* 懒汉式单例模式
* 本类描述怎么在懒汉单例模式中运用多线程同步锁
* 方法一:可以直接将getInstance()方法声明为synchronized的 但是这样做
* 会浪费资源 每次调用getInstance()方法时都要判断锁,所以可以按以下的方式解决
* 也就是方法二--两次判断,延时加载
*
* @author bing
*
*/
public class LazyMan {
private static LazyMan lm=null;
private LazyMan(){}
public static LazyMan getInstance()
{/**这里的处理方式为延迟加载
比如有线程A 进入 进入aaa判断 再进入bbb判断这时线程B启动 由aaa判断后 到
bbb此时 有线程锁的存在 线程B没有访问权限 再次回到线程A 线程A继续执行到
ccc这时判断后建立对象lm 线程A解锁 这时线程B再执行 判断ccc结果对象lm已经
存在了 所以不再执行。线程C启动 判断aaa时就lm已经存在了 所以也不会再继续
这样 就避免了对 线程锁的重复判断 节省资源---这种方式叫做两次判断,延时加载
*/
if(lm==null)//aaa
{
synchronized (LazyMan.class)//bbb
{
if(lm==null)//cccc
{
lm=new LazyMan();
}
}
}


return lm;
}
}


//饿汉式
package Single_Design_Pattern_Multithreading;
/**
* 饿汉式单例设计模式,不存在共享数据异常的问题 所以不用锁
* @author bing
*
*/
public class HungryMan_Single {
private static final HungryMan_Single hs=new HungryMan_Single();
private HungryMan_Single(){}
public static HungryMan_Single getInstance()
{
return hs;
}
}



====================================================================================================
死锁的问题 用以下的示例来说明死锁的问题

package Single_Design_Pattern_Multithreading;
/**
* 懒汉式单例模式
* 本类描述怎么在懒汉单例模式中运用多线程同步锁
* 方法一:可以直接将getInstance()方法声明为synchronized的 但是这样做
* 会浪费资源 每次调用getInstance()方法时都要判断锁,所以可以按以下的方式解决
* 也就是方法二--两次判断,延时加载
*
* @author bing
*
*/
public class LazyMan {
private static LazyMan lm=null;
private LazyMan(){}
public static LazyMan getInstance()
{/**这里的处理方式为延迟加载
比如有线程A 进入 进入aaa判断 再进入bbb判断这时线程B启动 由aaa判断后 到
bbb此时 有线程锁的存在 线程B没有访问权限 再次回到线程A 线程A继续执行到
ccc这时判断后建立对象lm 线程A解锁 这时线程B再执行 判断ccc结果对象lm已经
存在了 所以不再执行。线程C启动 判断aaa时就lm已经存在了 所以也不会再继续
这样 就避免了对 线程锁的重复判断 节省资源---这种方式叫做两次判断,延时加载
*/
if(lm==null)//aaa
{
synchronized (LazyMan.class)//bbb
{
if(lm==null)//cccc
{
lm=new LazyMan();
}
}
}


return lm;
}
}


//饿汉式
package Single_Design_Pattern_Multithreading;
/**
* 饿汉式单例设计模式,不存在共享数据异常的问题 所以不用锁
* @author bing
*
*/
public class HungryMan_Single {
private static final HungryMan_Single hs=new HungryMan_Single();
private HungryMan_Single(){}
public static HungryMan_Single getInstance()
{
return hs;
}
}

====================================================================================================
死锁的问题 用以下的示例来说明死锁的问题
package DemoSynchronized;

public class Ticket implements Runnable{
private int tickt=100;
Object obj = new Object();
public boolean flag=true;
public void run()//同步代码块
{
if(flag)
{
while(true)
{
synchronized (obj)//这里是同步代码块中有同步函数,注意锁是不同的
{
show();
}

}
}else
{
while(true)
{
show();
}
}

}

public synchronized void show()//同步函数这里的锁是this
{ //注意这里是同步函数里面有同步代码块而且两者的锁是不同的
synchronized(obj)//这里的锁是obj
{
if(tickt>0)
{
try{Thread.sleep(10);}catch(Exception e){}
System.out.println("同步函数进程---"+Thread.currentThread().getName()
+"-----sale:"+tickt--);
}
}

}
}
//测试程序代码如下:
package DeadLock;

import DemoSynchronized.Ticket;

/**
* 死锁:
* 同步中嵌套同步
* @author bing
*
*/
public class TestMain {
public static void main(String[] args) throws InterruptedException {
Ticket aTicket=new Ticket();
Thread t1=new Thread(aTicket);
Thread t2=new Thread(aTicket);
t1.start();//线程t1开始启动,启动的时间短,非常有可能被main主线程抢走执行权,所以让主线程休息一会
Thread.currentThread().sleep(16);
aTicket.flag=false;System.out.println("-------------------------------------");
t2.start();
}
}
//输出结果:输出过程中卡住 因为两个锁互相争夺

===========================================================================================
线程间通信的安全问题:
线程A对资源R进行加操作线程B对资源R进行减操作 如何同步 如何同步时安全操作 示例代码如下:

package DemoSynchronized;

public class Ticket implements Runnable{
private int tickt=100;
Object obj = new Object();
public boolean flag=true;
public void run()//同步代码块
{
if(flag)
{
while(true)
{
synchronized (obj)//这里是同步代码块中有同步函数,注意锁是不同的
{
show();
}

}
}else
{
while(true)
{
show();
}
}

}

public synchronized void show()//同步函数这里的锁是this
{ //注意这里是同步函数里面有同步代码块而且两者的锁是不同的
synchronized(obj)//这里的锁是obj
{
if(tickt>0)
{
try{Thread.sleep(10);}catch(Exception e){}
System.out.println("同步函数进程---"+Thread.currentThread().getName()
+"-----sale:"+tickt--);
}
}

}
}
//测试程序代码如下:
package DeadLock;

import DemoSynchronized.Ticket;

/**
* 死锁:
* 同步中嵌套同步
* @author bing
*
*/
public class TestMain {
public static void main(String[] args) throws InterruptedException {
Ticket aTicket=new Ticket();
Thread t1=new Thread(aTicket);
Thread t2=new Thread(aTicket);
t1.start();//线程t1开始启动,启动的时间短,非常有可能被main主线程抢走执行权,所以让主线程休息一会
Thread.currentThread().sleep(16);
aTicket.flag=false;System.out.println("-------------------------------------");
t2.start();
}
}
//输出结果:输出过程中卡住 因为两个锁互相争夺

===========================================================================================
线程间通信的安全问题:
线程A对资源R进行加操作线程B对资源R进行减操作 如何同步 如何同步时安全操作 示例代码如下:
package InOut_Demo;
/**
* 线程间通信的示例代码
* 线程间通信,其实就是多个线程在操作同一个资源,但是操作的动作是不同的
* @author bing
*
*/
public class TestMain {
public static void main(String[] args) {
Res r =new Res();
Input in=new Input(r);
OutPut ou = new OutPut(r);
Thread t1=new Thread(in);
Thread t2=new Thread(ou);
t1.start();
t2.start();
}


}

//资源R代码如下
package InOut_Demo;
/**
* 资源Res,Input和OutPut的操作对象
* @author bing
*
*/
public class Res {
private String name;
private String sex;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
}

//写操作代码如下
package InOut_Demo;
/**
* 对资源R进行写操作
* @author bing
*
*/
public class Input implements Runnable{
private Res r;
Input(Res r)
{this.r=r;}
public void run()
{ int x=0;
while(true)
{
synchronized (r) {
if(x==0){r.setName("mike");r.setSex("man");}
if(x==1){r.setName("王语嫣");r.setSex("女女");}
}

x=(x+1)%2;

}

}
}

//输出操作代码如下:
package InOut_Demo;
/**
* 输出资源R的内容
* @author bing
*
*/
public class OutPut implements Runnable{
private Res r;
public OutPut(Res r){this.r=r;}

public void run()
{
while(true)
{
synchronized(r){System.out.println(r.getName()+r.getSex());}

}

}
}
//测试程序代码如下
package InOut_Demo;
/**
* 线程间通信的示例代码
* 线程间通信,其实就是多个线程在操作同一个资源,但是操作的动作是不同的
* @author bing
*
*/
public class TestMain {
public static void main(String[] args) {
Res r =new Res();
Input in=new Input(r);
OutPut ou = new OutPut(r);
Thread t1=new Thread(in);
Thread t2=new Thread(ou);
t1.start();
t2.start();
}


}


经过测试 输出有序 说明线程间通信的安全问题解决
输出如下:
mikeman
mikeman
mikeman
王语嫣女女
王语嫣女女
王语嫣女女
==========================================================================================
等待唤醒机制
---接上个例子 要求 男女输出交替进行 也就是 写--男--输出--男---写--女---输出---女
资源类如下:

public class Res {
public String name;
public String sex;
public boolean flag=false;//旗帜变量用来控制是写资源还是输出资源
}
//写方法类如下:
public class Input implements Runnable{
public Res r;
private int x=0;
Input(Res r){this.r=r;}

public void run() {
while(true)
{
synchronized(r)
{
//如果旗帜为真,则等待 等待需要与锁一致
if(r.flag){try{r.wait();}catch(Exception e){System.out.println("Input wait 异常");}}
//如果旗帜为假 则写入资源
if(x==0){r.name="mike";r.sex="man";}
else{r.name="周芷若";r.sex="女女";}
x=(x+1)%2;
//写完资源后 资源设置为真
r.flag = true;
r.notify(); //唤醒,需要与锁一致
}
}
}
}

//输出资源类如下:
package Wait_WakeUp_right;

public class OutPut implements Runnable{
private Res r;
OutPut(Res r){this.r=r;}
public void run()
{
while(true)
{
synchronized(r)
{
if(!r.flag)//如果资源为假 则等待
{
try{r.wait();}catch(Exception e){System.out.println("output wait 出错");}
}
//如果资源为真 则输出资源
System.out.println(r.name+"。。。。"+r.sex);
r.flag = false;//输出后 资源设置为假
r.notify();//唤醒
}
}
}

}

//测试程序如下:
public class TestMain {

/**wait;
* notify;
* notifyALl; 都在同步中使用,因为要对持有监视器(锁)的线程操作。
* 所以都要使用在同步中,因为只有同步才有锁。
* 为什么这些操作线程的方法要定义在object类中?
* 因为这些方法在操作同步中线程时,都必须要标识他们所操作的线程持有的锁
* 只有同一个锁上的被等待线程可以被同一个锁上的线程notify唤醒,不可以对不同
* 锁中的线程进行唤醒
*
* 也即是说等待和唤醒必须是同一把锁。
* 而锁可以是任意对象,可以被任意对象调用的方法就定义在Object中
*
* @param args
*/
public static void main(String[] args) {
Res r=new Res();
Input in=new Input(r);
OutPut ou=new OutPut(r);
Thread t1=new Thread(in);
Thread t2=new Thread(ou);
t1.start();
t2.start();

}

}

输出结果如下:
mike。。。。man
周芷若。。。。女女
mike。。。。man
周芷若。。。。女女
mike。。。。man
周芷若。。。。女女
mike。。。。man
周芷若。。。。女女
mike。。。。man
周芷若。。。。女女
mike。。。。man-----------结果有序 而且交替输出
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值