1.进程:正在进行中的 程序(直译)(任务管理器中的都是进程)
分配程序的空间,不执行。
2.线程:就是进程中的一个负责程序执行的控制单元(执行路径)
一个进程中可以有多个执行路径,称之为多线程。
一个进程中至少要有一个线程。
开启多个线程是为了同时运行多个代码。
每一个线程都有自己运行的内容。这个内容称为线程要执行的任务。
其实应用程序的运行都是CPU在做着快速切换完成的。这个切换是随机的。
管理进程的运行:中央处理器CPU,快速切换执行的进程,看起来是同时执行。
(多核(CPU)电脑可以提高多进程运行速度,但是还是得看内存大小。)
2.1
多线程的好处:解决了多部分代码同时运行的问题。
多线程的弊端:开多了效率低。
2.2 JVM中的多线程解析:
①主线程:肯定有一条线程在执行main函数。
该线程的任务代码都定义在main函数中。
(主线程结束时,虚拟机不一定结束,还有其他线程)
②垃圾回收线程:还有一条线程在执行垃圾回收器。(其实每个对象都具备着被回收的方法,在Object.finalize(),这个方法由对象的垃圾回收器调用此方法,垃圾回收器在System.gc()静态方法)
该线程的任务代码定义在垃圾回收器中。
/*//让对象被回收,但是不是立即
class Demo
{
public void finalize() //其实没必要覆写
{
System.out.println(“demo ok”);
}
}
class ThreadDemo
{
public static void main(String[] args) //这是一个线程
{
new Demo();
new Demo();
System.gc(); //不是立即执行,这又是另一个线程,执行具有不确定性。
new Demo();
System.out.println(“HAHA”);
}
}
*/
等等。
2.3主线程运行示例:
3.多线程存在的意义
4.线程的创建方式
1)创建方式一:继承Thread类
步骤:
1.定义一个类继承Thread类。
2.覆盖Thead类中的run方法:
自定义的线程的任务通过Thread类中的run方法来体现。也就是说,run方法就是封装自定义线程运行任务的函数。
run方法中定义的就是线程要运行的任务代码。
开启线程是为了运行指定代码,所以只有继承Thread类,并复写run方法。
将运行的代码定义在run方法中即可。
3.直接创建Thread类的子类对象。
4.调用start方法开启线程并调用线程的任务run方法执行。
调用run和调用start的区别:
run:调用要在线程中执行的任务(代码)。
start:开启线程并调用线程的任务run方法执行。
代码:
class Demo extends Thread
{
private String name;
Demo(String name)
{
this.name = name;
}
public void run()
{
int[] arr = new int[3];
System.out.println(arr[3]); 发生异常,线程终止
show();
}
public void show()
{
for (int x = 0;x < 30 ;x++ )
{
// for (long y = 0; y <= 1000000000l;y++ ){} //long类型延迟比int明显
System.out.println(name+ "........x=" + x +".....name=" + Thread.currentThread().getName()); //getName()获取线程的名字(Thread-数字(从0开始))(但线程可能没有运行)。
//要获取运行时线程的名字,就得先通过静态方法Thread.currentThread()获得对当前正在执行的线程对象的引用。
}
}
}
class ThreadDemo2
{
public static void main(String[] args)
{
Demo d1 =new Demo("旺财"); //创建对象的时候就已经完成了线程名称的定义,所以.run()也会输出线程名称。
/* 源码: public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
*/
Demo d2 = new Demo("xiaoqiang");
d1.run(); //调用要在线程中执行的任务(代码),和正常调用没区别。
//这时返回的运行时线程名是 main
// d1.start(); //创建并启动线程。(使该线程开始执行;Java 虚拟机调用该线程的 run 方法。)
d2.start();
System.out.println(4/0); //发生算数异常,线程终止
System.out.println("over"); //主线程,这时一共由三个线程,随机执行
}
}
Thread类中的方法和名称:
方法:
1.getName():获取线程的名字(Thread-数字)(从0开始)(但线程可能没有运行)。
2.Thread.currentThread():静态方法,获得对当前正在执行的线程对象的引用。
线程的名称:
1.主函数的线程名为main
2.线程名自定义:
在子类的初始化函数中第一行添加super(name),name为自定义线程名。其实就是调用了Thread类的构造方法。
5.多线程运行图解:
1.线程之间独立运行,都有自己的运行空间,相互不影响。
2.线程发生异常立即跳栈,不再执行,不影响其他线程的执行。
主线程挂了,其他线程也不会立即结束,除非虚拟机关闭。
6.线程的状态:
CPU的执行资格:可以被CPU处理,在处理队列中排队
CPU的执行权:正在被CPU处理
7.线程创建的第二种方式:实现Runnable接口
步骤:
1.定义类实现Runnable接口。
2.覆盖接口中的run方法,将线程的任务代码封装到run方法中。
3.通过Thread类创建线程对象,并将Runnable接口的子类对象作为构造函数的参数进行传递。
为什么:因为线程的任务都封装在Runnable接口的子类对象的run方法中,
所以要在线程对象创建时就必须明确要运行的任务。
4.调用线程对象的start方法开启线程。
原因:
创建方式一,继承Thread类。当需要线程的类中已经继承了其他类,就不能再继承Thread类了。
这个时候还要用到额外的方法创建就需要使用接口。而Thread类已经实现了Runnable接口,它默认run方法就是覆盖的Runnable接口的run抽象方法,不执行任何操作。并且Thread类提供了可以带Runnable接口对象的构造方法,来分配新的 Thread 对象,调用新对象的run方法。
内部实现的思想细节:
class Thread implements Runnable
{
private Runnable r;
Thread()
{
}
Thread(Runnable r) //第二种创建方式
{ //在新的Thread对象中覆盖Runnable的run方法。
this.r = r;
}
public void run()
{
if(r!=null)
r.run();
}
public void start()
{
run();
}
}
class SubThread extends Thread
{ //第一种创建方式
public void run() //覆盖Thread中的run()
{
}
}
SubThread s =new SubThread();
s.start(); //调用的是父类的start()和子类覆盖的run()
class ThreadImpl implements Runnable
{
public void run(); //第二种创建方式
}
Thread t = new Thread(new ThreadImpl());
t.start();
API说明:
Thread
public Thread(Runnable target)
分配新的 Thread 对象。这种构造方法与 Thread(null, target,gname) 具有相同的作用,其中的 gname 是一个新生成的名称。自动生成的名称的形式为 “Thread-”+n,其中的 n 为整数。
代码:
class Demo implements Runnable
{
public void run()
{
show();
}
void show()
{
for (int x = 0; x <20 ;x++ )
{
System.out.println(Thread.currentThread().getName() + "......" + x);
}
}
}
class ThreadDemo3
{
public static void main(String[] args)
{
Thread t1 = new Thread(new Demo()); //Thread类是在构造方法中定义的是Runable接口中自己覆盖的run方法,
//即类Thread实现了Runable接口
/*API解释:
run
public void run()
如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。
Thread 的子类应该重写该方法。
*/
//所以赋值一个实现Runable接口的子类,调用的就是子类覆盖的的run方法。
Thread t2 = new Thread(new Demo());
t1.start(); //开启线程并调用线程中的run方法执行
t2.start();
}
}
第二种方式的好处:
第一种方式,继承了Thread,会变成Thread体系中的一员,具备了Thread类中的所有方法。但是用类描述事物完了后,如果仅仅只是需要将一部分代码被对线程所操作的话,就没有必要去具备线程对象中的所有方法。做继承的目的仅仅是为了覆盖run方法,建立线程运行的任务。
所以有了第二种方式,实现Runnable接口,它的出现仅仅是将线程的任务进行了对象的封装,不需要线程出现。如果需要有多线程,就去实现Runnable接口,把任务代码封装在run方法中,变成Runnable接口的子类对象,就是线程任务对象。(这是一种思想的变化)
Runnable r = new Student();
要运行多线程时,直接创建Thread对象,在创建对象的同时,明确线程任务对象就行了。
Thread t = new Thread(r);
t.start();
Thread类和其他需要多线程的类中都存在run()方法,向上抽取,而线程是事物的一个额外功能,所以抽取并实现了Runnable接口。
实现Runnable的好处:
1.将线程的任务从线程的子类中分离出来,进行了单独的封装。
按照面向对象的思想将任务封装成对象。
2.避免了Java单继承的局限性。
所以,创建线程的第二种方式较为常见。
8.多线程小例子:
/*
卖票示例:
需求:四个窗口同时卖票
问题:
1.票数共用 : 就只能使用一个对象,而继承方式需要创建四个对象,所以用接口定义方式创建线程。
2.保证每次买票过程能完成:
就是将多条操作贡献数据的线程代码封装起来,当有线程在执行这些代码的时候,其他线程不可以参与运算。
必须要当前线程把这些代码都执行完毕后,其他线程才可以参与运算。
在Java中,用同步代码块姐可以解决这个问题。
同步代码块的格式:
synchronized(对象)
{
需要被同步的代码;
}
多次启用一个线程是非法的。
*/
class Ticket implements Runnable // extends Thread //继承无法实现同时卖100张票完成。
{
private int num = 100; //静态化后,对象就没有意义。而且要卖另外不同的100张票呢。
Object obj = new Object(); //保证用的是同一个锁
public void run()
{
// Object obj = new Object(); //这样会有4把锁,同步会失效
sale();
}
public void sale() //不能抛出异常,因为实现的接口没有抛
{
while(true)
{
//同步代码块
synchronized(obj)
{
if(num >0)
{
/* try
{
Thread.sleep(10); //线程安全问题测试
}
catch (InterruptedException e)
{
}
*/
System.out.println(Thread.currentThread().getName() + ":余票:"+ --num);
}
}
}
}
}
class TicketDemo
{
public static void main(String[] args)
{
Ticket t = new Ticket(); //创建一个线程任务对象,火车票
Ticket tt = new Ticket(); //卖第二种票,动车票
Thread t1 = new Thread(t,"窗口1(火车票)");
Thread t2 = new Thread(t,"窗口2(火车票)");
Thread t3 = new Thread(tt,"窗口3(动车票)"); //窗口3,4 买动车票
Thread t4 = new Thread(tt,"窗口4(动车票)");
t1.start();
t2.start();
t3.start();
t4.start();
// t1.start();
// t1.start(); //这里会导致 主线程 发生线程状态异常。
// t1.start();
// t1.start();
}
}
问题分析:
线程安全问题的现象:
if(num >0)
System.out.println...
当num = 1 的时候:
第一个线程刚刚进去if循环准备执行输出语句的时候,cpu不调用它了,使其进入临时阻塞状态。
然后cpu调用线程二进来执行输出语句,输出1,再--,0。
这时cpu回来调用线程一,这个时候不需要判断直接执行了输出语句,输出0,再--,num的值为 -1。显然0号票不合理。
产生安全问题的原因:
(因为num是共享的数据,多个线程操作时相互之间可能有影响。)
1.多个线程在操作共享数据。
(如果只有一个语句,不会出事。
两条以上可能导致问题,因为可能在结束一条语句时,其他线程进来了,使当前线程暂停进入临时阻塞状态。而等其他线程操作完,再回来执行该线程的第二条语句时,共享数据可能就已经发生了改变。)
2.操作共享数据的线程代码有多条。
当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算,就会导致线程安全问题的产生。
解决问题:同步代码块
就是将多条操作贡献数据的线程代码封装起来,当有线程在执行这些代码的时候,其他线程不可以参与运算。
必须要当前线程把这些代码都执行完毕后,其他线程才可以参与运算。
在Java中,用同步代码块姐可以解决这个问题。
同步代码块的格式:
synchronized(对象)
{
需要被同步的代码;
}
同步的好处和弊端:
原理:在同步代码块中,当第一个线程进来时,获取obj对象,开始执行代码。这时没有obj对象,其他线程无法进来,只有等到当前进程结束后四方obj对象。obj对象像个锁,就叫对象锁(同步锁)。另外当前线程是不会一直有执行权的,只是切其他线程时它们进不来,只有再次切到当前线程,直到当前线程执行结束。
好处:解决了线程的安全问题。
弊端:相对降低了效率,因为同步外的线程都会判断同步锁。
同步的前提:
同步中必须有多个线程,并使用的是同一个锁。
9.同步函数:同步的第二种表现形式
在函数声明中添加修饰符synchronized即可。
/*
需求:储户,两个,每个都到银行存钱,每次存100,共存三次。
*/
class Bank
{
private int sum; //共享数据
// private Object obj = new Object(); //唯一对象锁
public synchronized void add(int num)
{
// synchronized(obj){ //这就是函数的全部代码,直接定义同步函数
sum = sum +num;
//这里会出现安全隐患。
System.out.println("sum = " + sum);
//}
}
}
class Cus implements Runnable
{
Bank b = new Bank();
public void run()
{
for (int x = 0; x < 3 ; x++ )
{
b.add(100);
}
}
}
class BankDemo
{
public static void main(String[] args)
{
Cus c = new Cus();
Thread t1 = new Thread(c);
Thread t2 = new Thread(c);
t1.start();
t2.start();
}
}
验证同步函数的锁:
验证结果是不是同一个锁,即同步函数锁用的不是obj。
而是调用该方法的对象,this
注意:
但是当同步函数设置成静态的时候,对象锁就是这个方法所属类的字节码对象。getClass()可以获取该对象所属类的的字节码对象,而字节码对象是Class类的对象。
就是同步代码块中写this.getClass()可以获得和静态同步函数一样的同步锁。
另一种表示方式:类名.class;也是返回所属类的字节码对象。class是Class类的一个静态属性,而所有的类在加载时都是以Class类的对象形式加载的。
API中说明:
getClass:
返回的 Class 对象是由所表示类的 static synchronized 方法锁定的对象。
同步函数和同步代码块的区别:
同步函数的锁是固定的this。
同步代码块的锁是任意的对象(自定义,也不一定是obj)。
开发建议使用同步代码块。同步函数可以写成同步代码块的简写形式。一简写,就有前提(如果用的锁是this),有好处,有弊端。
验证代码:
/*
同步函数锁验证:
为了验证同步函数和同步代码块的锁,
窗口一设置同步函数,
窗口二设置同步代码块,如果是同一个对象锁,则不会输出负值。
结果是不是同一个锁,即同步函数锁用的不是obj。
而是调用该方法的对象,this
*/
class Ticket implements Runnable // extends Thread //继承无法实现同时卖100张票完成。
{
private int num = 100; //静态化后,对象就没有意义。而且要卖另外不同的100张票呢。
private Object obj = new Object(); //验证用
boolean flag = true;
public void run()
{
sale();
}
// public synchronized void sale()
public void sale()
{
if(flag)
{
while(true) //同步函数sale后,第一个线程进来就出不去了,着是死循环。所以不应该同步sale函数。
{ //解决方法:将死循环里的代码封装成同步函数即可。
show(); //窗口一设置同步函数
}
}
else //窗口二设置同步代码块,如果是同一个对象锁,则不会输出负值。
{
while(true)
{
synchronized(obj) //改成synchronized(this),就不会出现错误值
//如果同步函数是静态的,则改成synchronized(this.getClass()),通过getClass()获得本类的 static synchronized 方法锁定的对象。
//或者synchronized(Ticket.class),等效
{
if(num >0)
{
try{Thread.sleep(10); }catch (InterruptedException e){} //线程安全问题测试
System.out.println(Thread.currentThread().getName() + ":余票(obj):"+ --num);
}
}
}
} }
private synchronized void show() //同步锁不是obj,是this,即new Ticket()。
//如果改成静态的,则同步锁对象就是所属类的字节码对象。
{
if(num >0)
{
try{Thread.sleep(10); }catch (InterruptedException e){} //线程安全问题测试
System.out.println(Thread.currentThread().getName() + ":余票:(function)"+ --num);
}
}
}
class SynFunctionLockDemo
{
public static void main(String[] args)
{
Ticket t = new Ticket(); //创建一个线程任务对象,火车票
Thread t1 = new Thread(t,"窗口1"); //为了验证同步函数和同步代码块的锁。设置同步函数
Thread t2 = new Thread(t,"窗口2");//设置同步代码块
t1.start();
//这里为了让线程一能够有完成 flag =true的赋值动作,让主线程暂停一下。
try{Thread.sleep(100); }catch (InterruptedException e){} //线程安全问题测试
t.flag = false;
t2.start();
}
}
10.单例模式涉及的多线程问题。
关于懒汉式单例设计模式在多线程中的
面试问题:
1.安全么? 不安全
2.写个同步函数,效率高么?同步锁是哪一个?怎么解决?
不高,因为每次获取对象都要判断同步锁。
静态同步函数的同步锁是所在类的字节码对象,可以通过类名.class来获取。
解决办法:通过同步代码块把创建对象的代码封装起来,再放到对象是否为空的判断里,这样就只用判断一次同步锁,提高效率。
/*
多线程下的单例:
*/
//饿汉式
class Single
{
private Single(){}
private static Single s = new Single();
public static void getInstance()
{
return s; //一句话,不存在安全隐患。
}
}
//懒汉式
class Single
{
private Single(){}
private static Single s =null;
public static/* synchronized*/ getInstance() //解决办法1:加同步函数,但是每次拿对象都要判断同步锁,效率低。
{
if(s==null) //解决办法2:通过同步代码块把创建对象的代码封装起来,再放到判断里,这样就只用判断一次同步锁。
{ //加判断是解决效率问题
synchronized(Single.class) //加同步是解决安全问题
{
if(s == null)
//线程0进来,切换到线程1,线程1暂停,再切到线程0,建立返回一个对象,
//然后切换到线程1,不用判断,又建立一个对象。此时无法保证对象的唯一性了。存在安全隐患。
s = new Single();
}
}
reruen s;
}
}
class SingleDemo
{
public static void main(String[] args)
{
System.out.println("Hello World!");
}
}
11.死锁示例:
/*
死锁:常见情景之一:同步嵌套
*/
class Ticket implements Runnable
{
private int num = 100;
private Object obj = new Object();
boolean flag = true;
public void run()
{
sale();
}
public void sale()
{
if(flag)
{
while(true)
{
synchronized(obj)
{
show();
}
}
}
else
{
while(true)
{
show(); //线程二拿着this锁进obj锁,同时发生导致程序死锁
}
}
}
private synchronized void show() //线程一拿着obj锁进this锁,同时发生导致程序死锁
{
synchronized(obj)
{
if(num >0)
{
try{Thread.sleep(10); }catch (InterruptedException e){}
System.out.println(Thread.currentThread().getName() + ":余票(obj):"+ --num);
}
}
}
}
class DeadLockDemo
{
public static void main(String[] args)
{
Ticket t = new Ticket();
Thread t1 = new Thread(t,"窗口1");
Thread t2 = new Thread(t,"窗口2");
t1.start();
try{Thread.sleep(100); }catch (InterruptedException e){}
t.flag = false;
t2.start();
}
}
死锁程序:面试用
class Test implements Runnable
{
private boolean flag;
Test(boolean flag)
{
this.flag = flag;
}
public void run()
{
if(flag)
{
while(true) //增加死锁概率
{
synchronized(MyLock.locka)
{
System.out.println(Thread.currentThread().getName()+ "...if locka...");
synchronized(MyLock.lockb)
{
System.out.println(Thread.currentThread().getName()+"...if lockb...");
}
}
}
}
else
{
while(true)
{
synchronized(MyLock.lockb)
{
System.out.println(Thread.currentThread().getName()+"...else lockb...");
synchronized(MyLock.locka)
{
System.out.println(Thread.currentThread().getName()+"...else locka...");
}
}
}
}
}
}
class MyLock
{
public static final Object locka = new Object();
public static final Object lockb = new Object();
}
class DeadLockTest
{
public static void main(String[] args)
{
Test a = new Test(true); //因为是boolean型变量,值是固定的 。所以多个内容没有影响
Test b = new Test(false);
Thread t1 = new Thread(a);
Thread t2 = new Thread(b);
t1.start();
t2.start();
}
}