一.多线程概述
一、进程和线程
进程 正在进行的程序,一个进程可以有多条执行路径,这每一个路径,就称为线程,一个进程至少有一个线程
线程,程序中的控制单元或者执行路径,线程控制着进程的执行
创建线程的目的?为了让线程去执行一些代码。
1. 主线程----vm执行的线程 main线程由jvm开启。一句话总结:主线程负责开启其他线程!
2. 垃圾回收线程---更细节说明jvm,jvm启动的 不止一个主线程,还有垃圾回收线程【一般做题慎重考虑】
二、多线程存在的意义,
【多个程序同时执行】---Cpu在某一时刻只能执行一个程序,程序的同时执行 是cpu在快速切换,
三、线程的创建方式以及注意点总结
1. 有两种方法
方式一:继承Thread类,复写Thread类中的run方法--创建子类对象【就是创建好了一个线程】--对象.start()启动线程,
方法二:继承Runnable接口,实现run方法,通过Thread创建线程,接收Runnble子类对象,,再通过线程对象start方法启动线程
2. 为什么要覆盖run方法?
Thread类和Runnable接口中的run方法,用于存储线程运行的代码。子类复写run()
方法就是为了封装自定义线程要执行的代码
【科普:Thread类具有调用底层的方法,控制windows系统创建线程】
3. start()和run()的区别?
【start方法:开启线程并执行该线程的run方法】
【对象.run只是对象调用该方法,线程创建了,并没有执行,run方法只是封装了自定义线程要执行的代码】
4.Runnable接口和Thread类的区别
① Thread类【线程类】也是继承了Runnable的run方法, run方法的作用,只是存放需要通过线程执行的语句,即只是一个存放功能
② 如果要创建线程,必须通过Thread类或者Thread子类对象启动
四、多线程的特性---随机
a. 现象:程序运行结果每一次都不同
是因为多个线程都获取cpu的执行权,cpuu执行到谁,就运行谁,【单核中,某一时刻,只有一个程序在运行】
b. 随机性:谁抢到cpu的执行权,就执行谁,至于执行多长时间,是cpu说了算,
五.获取线程对象和名称
currentThread() 返回对当前正在执行的线程对象的引用。
setName() 设置线程名称。
getName():获取线程名称 Thread.currentThread.getName();
.线程的状态
二.多线程示例
多线程卖票:
class Demo2 implements Runnable
{
private int ticket =30;
public void run()
{
for(;ticket>0;)
{
try
{
Thread.sleep(100);//定义在for循环之内!
}
catch (InterruptedException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread()+" run "+ticket--);
}
}
}
public class Ticket2 {
public static void main(String[] args) {
// TODO Auto-generated method stub
Demo2 d=new Demo2();
//4个线程 只关联接收一个 run方法和一个成员 ticket,表示共享同一个对象的成员数据
Thread t1=new Thread(d);
Thread t2=new Thread(d);
Thread t3=new Thread(d);
Thread t4=new Thread(d);
//启动线程
t1.start();
t2.start();
t3.start();
t4.start();
}
}
出现了多线程的安全问题
同票:没关联对象,没有共享ticket的值
错票: 0和负数原因
当多条语句在操作同一个线程共享数据ticket时,一个线程对共享的【多条语句】只执行了一部分,就被另一个线程抢走了运行权,导致共享数据错误。
如何解决?
对多条操作共享数据的语句,只能让一个线程都执行完。在执行过程中,其他线程不可以参与执行。
Java对于多线程的安全问题提供了专业的解决方式,这就引入了线程的同步
多线程存钱
/*
需求:
银行有一个金库。
有两个储户分别存300员,每次存100,存3次。
目的:该程序是否有安全问题,如果有,如何解决?
如何找问题:
1,明确哪些代码是多线程运行代码。
2,明确共享数据。
3,明确多线程运行代码中哪些语句是操作共享数据的。
*/
class Banke
{
//累加存储功能
private int sum;//多线程共享数据
public synchronized void add(int i)//多线程共同执行的语句,避免多线程安全
{
sum=sum+i;
System.out.println(Thread.currentThread()+"sum"+sum);
}
}
class Cus implements Runnable
{
private Banke b = new Banke();//定义对象操作Banke类的add方法
public void run()
{
for(int i=0;i<3;i++)
{
b.add(100);
}
}
}
public class Bank1 {
public static void main(String[] args) {
// TODO Auto-generated method stub
Cus c=new Cus();
Thread t1=new Thread(c);
Thread t2=new Thread(c);
t1.start();
t2.start();
}
}
三、线程的同步
1、同步代码块
(1)格式:
synchronized(对象)
{
需要被同步的代码}
对象:如同锁
持有锁的线程可以在同步中执行。
没有锁的线程即使获取CPU的执行权,也进不去,因为没有获取锁。(2)同步的前提:
1:必须要有两个或者两个以上的线程。
2:必须是多个线程使用同一个锁。
3:必须保证同步中只能有一个线程在运行。
(3)好处:解决了多线程的安全问题。弊端:多个线程需要判断锁,较为消耗资源。
例如火车上的卫生间经典示例!
卫生间的门关上并锁死,表示里面有人使用,外面的人进不来,只能等里面的人使用完出来才能进入;
卫生间的门开着,表示里面没有人,外面的人可以进来使用,进来一个人把门关上锁死后,外面的人就又进不来了。
2、同步函数
同步函数就是在方法前面修饰synchronized关键字的方法。
当某个对象调用了同步方法时,该对象上的其他同步方法必须等待该同步方法执行完毕后才能执行,必须将每个能访问共享资源的方法修饰为同步方法,否则会出错。
3.同步函数和同步代码块的区别?
同步函数简化了代码书写,减少了代码的缩进,但是同步代码块更灵活,可以嵌套在方法体中
4.小知识点:
若在run方法前加同步关键字,则为单线程
同步函数的锁是this
静态同步函数的锁,使用的是Class对象作为锁 类名.class
5.单例设计模式----懒汉式
懒汉式 ---延迟加载
懒汉式 ---延迟加载
class Single
{
private static Single s=null;
private Single(){}
public static void getInstance()
{
if(s==null)
{
synchronized(Single.class)
{
if(s==null)
s=new Single();
}
}
return s;//这一步经常忘记
}
记住懒汉式常问的问题!还有为什么懒汉式叫延迟加载?
1.懒汉式和饿汉式有什么不同:懒汉式 是实例的延迟加载
2.懒汉式有安全隐患吗?多线程访问的时,会出现多个实例的安全问题
3.安全隐患解决办法:加同步代码块或者同步函数,但是比较低效,可以使用双重判断+内部同步代码块的方式解决低效的问题
4.懒汉式的锁是:该类所属的字节码对象
6.死锁示例
死锁是指发生在线程之间相互阻塞的现象,这种现象导致同步线程相互等待,以致每个线程都不能往下执行。发生在同步的嵌套中。
下面写一个简单的死锁
<span style="font-size:18px;">class Test implements Runnable
{
boolean flag;
public Test(boolean b)
{
this.flag=b;
}
public void run()
{
if(flag)
{
while(true)
{
synchronized (Lock.locka)
{
System.out.println(Thread.currentThread()+"locka if!");
synchronized(Lock.lockb)
{
System.out.println(Thread.currentThread()+"lockb if!");
}
}
}
}
else
{
while(true)
{
synchronized (Lock.lockb)
{
System.out.println(Thread.currentThread()+"locka else!");
synchronized(Lock.locka)
{
System.out.println(Thread.currentThread()+"lockb else!");
}
}
}
}
}
}
//定义锁</span>
<span style="font-size:18px;">class Lock
</span>
<span style="font-size:18px;">{
static Object locka=new Object();
static Object lockb=new Object();
}
public class DeadLock {
public static void main(String[] args) {
// TODO Auto-generated method stub
//定义线程并且启动</span>
<span style="font-size:18px;"> Thread s1=new Thread(new Test(true));
Thread s2=new Thread(new Test(false));
s1.start();
s2.start();
}
}</span>
死锁问题发生:
线程1有a锁,线程2有b锁;线程1想要访问b锁,线程2想要访问a锁,双方还不放自己的锁,于是产生死锁。
易引起 是因为两个锁引用的不是同一个锁,这样就会引起死锁现在
还有在多现在中出现安全性问题的时候,一般要考虑的问题:
1. 是不是两个线程,并且两个线程有没有同步,也就是有没有 synchronized 块或函数
2. 如果有多个同步代码块或同步函数的话,那看看所有 的同步它们用的是不是同一个锁,如果不是,会引起死锁和安全性问题
四.多线程的操作【难不理解】
1、线程通信
(1)线程通信
多线程一个重要特点就是它们之间可以相互通信,线程通信是线程之间可以相互交流和等待,可以通过经常共享的数据使线程相互交流,也可以通过线程控制方法使线程之间相互等待。
线程间通信:其实就是多个线程在操作同一个资源,但是操作的动作不同。
(2)等待唤醒机制
线程通信可以通过线程控制方法使线程互相等待。
Object类提供了3个方法:wait()、notify()和notifyAll()。
都使用在同步中,因为要对持有监视器(锁)的线程操作。
所以要使用在同步中,因为只有同步才具有锁。
为什么这些操作线程的方法要定义Object类中呢?
因为这些方法在操作同步中线程时,都必须要标识它们所操作线程只有的锁,
只有同一个锁上的被等待线程,可以被同一个锁上notify唤醒。
不可以对不同锁中的线程进行唤醒。
也就是说,等待和唤醒必须是同一个锁。
而锁可以是任意对象,所以可以被任意对象调用的方法定义Object类中。
wait()和sleep()的区别:wait() 释放资源,释放锁;sleep() 释放资源,不释放锁。
五.线程小知识点
1.停止线程,守护线程【守护线程就是后台线程】,join方法,yield()方法
Interrupt()
setDaemon(boolean on)该方法必须在启动前调用
后台线程的特点:开启后,和前台线程共同抢夺cpu执行权,当所有的前提线程都结束后,后台线程会自动结束
Join方法;等待该线程停止,即,cpu的执行权在调用join方法的线程手里,而等这个线程执行完毕之后,线程.join之后的线程交互执行,
2.线程池
线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。线程池线程都是后台线程。每个线程都使用默认的堆栈大小,以默认的优先级运行,并处于多线程单元中。如果某个线程在托管代码中空闲(如正在等待某个事件),则线程池将插入另一个辅助线程来使所有处理器保持繁忙。
3.Java线程中sleep和wait的区别,
java 线程中的sleep和wait有一个共同作用,停止当前线程任务运行,但他们存在一定的不同,首先我们先看sleep中的构造函数。
1、这两个方法来自不同的类分别是Thread和Object
2、最主要是sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。
3、wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在
任何地方使用(使用范围)
synchronized(x){
x.notify()
//或者wait()
}
4、sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常