——Java培训、Android培训、iOS培训、.Net培训、期待与您交流! ——-
多线程:
进程:正在进行中的程序(直译)
线程:进程中一个负责程序执行的控制单元(执行路径)
线程控制着进程的执行
- 一个进程中可以有多个执行路径,称之为多线程。
- 一个进程中至少要有一个线程。
- 开启多个线程是为了同时运行多部分代码,每一个线程都有自己运行的内容,这个内容可以称为线程要执行的任务。
多线程的好处:解决了多部分代码同时运行的问题。
多线程的弊端:线程太多,会导致效率的降低。
JVM启动时启动了多条线程,至少有两个线程可以分析的出来:
- 执行main函数的线程,该线程的任务代码都定义在main函数中。
- 负责垃圾回收的线程。
创建线程方式一:继承Thread类
- 定义一个类继承Thread类。
- 覆盖Thread类中的run方法。
- 直接创建Thread的子类对象创建线程。
- 调用start方法开启线程并调用线程的任务run方法执行。
//需要多线程执行的类要继承Thread类
class Demo extends Thread{
private String name ;
Demo(String name){
this.name = name;
}
//Run方法里定义的是线程要运行的任务代码
public void run(){
for(int x = 0; x < 10; x++){
//Thread.currentThread ():获取调用这个方法的线程对象;
//.getName():通过线程对象获取线程名称
System.out.println(name + "...x=" + x + "...ThreadName=" + Thread.currentThread ().getName());
}
}
}
class ThreadDemo{
//主线程
public static void main(String[] args){
//创建类的两个对象
Demo d1 = new Demo("旺财");
Demo d2 = new Demo("xiaoqiang");
//开启线程,调用run方法。
d1.start(); //开启额外的线程1
d2.start(); //开启额外的线程2
//线程1、线程2会与主线程抢占cpu资源
for(int x = 0; x < 20; x++){
System.out.println("x = " + x + "...over..." + Thread.currentThread().getName());
}
}
}
创建线程方式二:实现Runnable接口
- 定义类实现Runnable接口。
- 覆盖接口中的run方法,将线程的任务代码封装到run方法中。
- 通过Thread类创建线程对象,并将Runnable接口的子类对象作为Thread类的构造函数的参数进行传递。
- 调用线程对象的start方法开启线程。
实现Runnable接口的好处:
- 将线程的任务从线程的子类中分离出来,进行了单独的封装,按照面向对象的思想将任务封装成对象。
- 避免了Java单继承的局限性。所以,创建线程的第二种方式较为常用。
//创建类实现Runnable接口
class Demo implements Runnable{
//将要运行的代码封装到run方法中
public void run(){
show();
}
public void show(){
for(int x = 0; x < 20; x++){
//获取线程名称
System.out.println(Thread.currentThread().getName() + "..." + x);
}
}
}
class ThreadDemo{
public static void main(String[] args){
//创建Demo类的一个对象
Demo d = new Demo();
//把Demo类对象作为参数创建Thread的两个对象
Thread t1 = new Thread(d);
Thread t2 = new Thread(d);
//执行Thread类对象,开启线程
t1.start();
t2.start();
}
}
线程安全问题:
- 多个线程在操作共享的数据。
- 操作共享数据的线程代码有多条。
当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算,就会导致线程安全问题的产生。
同步:
用于解决线程安全问题
将多条操作共享数据的线程代码封装起来,当有线程在执行这些代码的时候,其他线程不可以参与运算。必须要当前线程把这些代码都执行完毕后,其他线程才可以参与运算。
使用同步的前提:
- 必须有两个或两个以上线程
- 必须是多个线程使用同一个锁
同步的好处:解决了线程的安全问题。
同步的弊端:当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。
把需要被同步的代码放进synchronized里面
同步代码块的格式:
synchronized(任意对象(即锁)){
需要被同步的代码;
}
class Bank{
private int sum ;
public void add(int num){
//用synchronized把需要同步的代码括起来,锁设为this
synchronized(this ){
sum = sum + num;
System. out.println("sum = " + sum);
}
}
}
class Cus implements Runnable{
private Bank b = new Bank();
//把需要多线程执行的代码放在run方法里面
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();
}
}
同步函数:
在需要同步的代码的函数上加上synchronized修饰符,锁默认固定为this
静态的同步函数使用的锁是该函数所属字节码文件对象,可以用getClass方法获取,也可以用当前类名.class表示。
class Bank{
private int sum ;
//在有安全问题的函数上加上synchronized修饰符
public synchronized void add(int num){
sum = sum + num;
System.out.println("sum = " + sum);
}
}
多线程下的单例模式:
饿汉式不存在安全问题,因为不存在多个线程共同操作数据的情况。
懒汉式会有安全问题,因为他满足了同步问题的条件:
1. 多个线程在操作共享的数据。
2. 操作共享数据的线程代码有多条。
所以要对懒汉式进行同步操作。
因为每次调用到对象都要先判断对象是否存在,如果直接使用同步之后再判断这种方式的话,创建对象之后的判断也会同步,效率太低,因此要使用双重判断的方式。
class Single{
private static Single s = null;
private Single(){}
public static Single getInstance(){
//先判断对象是否有被创建,解决创建了对象之后的效率问题
if(s ==null){
//之后再使用同步
synchronized(Single.class){
//使用同步之后再进行一次判断,解决安全问题
if(s == null)
//如果对象不存在则创建
s = new Single();
}
}
return s ;
}
}
死锁:
死锁常见情景之一:同步的嵌套。
当一段代码需要拿到锁1跟锁2,而另一段代码需要拿到锁2跟锁1,就很容易出现一个线程拿到了锁1,而另一个线程拿到锁2,都在等待对方的锁,而又不释放锁,就会造成死锁。写代码时要注意避免这种情况。
线程间通信:
多个线程在处理同一资源,但是任务却不同,这时候就需要线程间通信。
等待/唤醒机制涉及的方法:
- wait():释放执行权,释放锁,让线程处于冻结状态,被wait的线程会被存储到线程池中。
- notify():唤醒线程池中的一个线程(任何一个都有可能)。
- notifyAll():唤醒线程池中的所有线程。
线程间通信容易出现死锁问题,要注意
JDK1.5新特性:
同步代码块就是对于锁的操作是隐式的。
JDK1.5以后将同步和锁封装成了对象,并将操作锁的隐式方式定义到了该对象中,将隐式动作变成了显示动作。
Lock接口:出现替代了同步代码块或者同步函数,将同步的隐式操作变成显示锁操作。同时更为灵活,可以一个锁上加上多组监视器。
lock():获取锁。
unlock():释放锁,为了防止异常出现,导致锁无法被关闭,所以锁的关闭动作要放在finally中。
Condition接口:出现替代了Object中的wait、notify、notifyAll方法。将这些监视器方法单独进行了封装,变成Condition监视器对象,可以任意锁进行组合。
- Condition接口中的await方法对应于Object中的wait方法。
- Condition接口中的signal方法对应于Object中的notify方法。
- Condition接口中的signalAll方法对应于Object中的notifyAll方法。
/*
使用一个Lock、一个Condition修改多生产者-多消费者问题。
*/
import java.util.concurrent.locks.*;
class Resource{
private String name ;
private int count = 1;
private boolean flag = false;
//创建一个锁对象
Lock lock = new ReentrantLock();
//通过已有的锁获取该锁上的监视器对象
Condition con = lock .newCondition();
public void set(String name){
//获取锁lock
lock.lock();
try{
//判断标记,为假就进行生产操作,为真就进入等待池
while(flag )
try{
//线程释放锁,进入等待池
con.await();
} catch(InterruptedException e){
e.printStackTrace();
}
//进行生产操作
this.name = name + count;
count++;
System.out.println(Thread.currentThread().getName() + "...生产者..." + this. name);
//生产操作完成后,把标记置换为真
flag = true ;
//生产完成后唤醒该锁等待池中所有线程
con.signalAll();
}finally{
//最后一定要释放锁
lock.unlock();
}
}
public void out(){
//获取锁lock
lock.lock();
try{
//判断标记,为真就进行消费操作,为假就进入等待池
while(!flag )
try{
con.await();
} catch(InterruptedException e){
e.printStackTrace();
}
//进行消费操作
System.out.println(Thread.currentThread().getName() + "...消费者..." + this. name);
//消费操作完成后,把标记置换为假
flag = false ;
//生产完成后唤醒该锁等待池中所有线程
con.signalAll();
}finally{
//最后一定要释放锁
lock.unlock();
}
}
}
class Producer implements Runnable{
private Resource r ;
//通过构造函数传递资源对象
Producer(Resource r){
this.r = r;
}
public void run(){
while(true ){
//循环生产商品“烤鸭”
r.set( "烤鸭");
}
}
}
class Consumer implements Runnable{
private Resource r ;
//通过构造函数传递资源对象
Consumer(Resource r){
this.r = r;
}
public void run(){
while(true ){
//循环消费商品
r.out();
}
}
}
class ProducerConsumerDemo {
public static void main(String[] args){
Resource r = new Resource();
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
//通过Producer、Consumer对象创建线程
Thread t0 = new Thread(pro);
Thread t1 = new Thread(pro);
Thread t2 = new Thread(con);
Thread t3 = new Thread(con);
//启动线程
t0.start();
t1.start();
t2.start();
t3.start();
}
}
/*
使用一个Lock、两个Condition修改上面的多生产者-多消费者问题。
*/
import java.util.concurrent.locks.*;
class Resource{
private String name ;
private int count = 1;
private boolean flag = false;
//创建一个锁对象
Lock lock = new ReentrantLock();
//通过已有的锁获取该锁上的监视器对象
Condition con = lock .newCondition();
//通过已有的锁获取两组监视器,一组监视生产者,一组监视消费者
Condition producer_con = lock .newCondition();
Condition consumer_con = lock .newCondition();
public void set(String name){
lock.lock();
try{
while(flag )
try{
//这时进入的是producer_con的等待池
producer_con.await();
} catch(InterruptedException e){
e.printStackTrace();
}
this.name = name + count;
count++;
System.out.println(Thread.currentThread().getName() + "...生产者..." + this. name);
flag = true ;
//生产完之后唤醒consumer_con等待池中的一个线程来消费
consumer_con.signal();
} finally{
lock.unlock();
}
}
public void out(){
lock.lock();
try{
while(!flag )
try{
//进入的是consumer_con的等待池
consumer_con.await();
} catch(InterruptedException e){
e.printStackTrace();
}
flag = false ;
//消费完之后唤醒producer_con等待池中的一个线程接着生产
producer_con.signal();
System.out.println(Thread.currentThread().getName() + "...消费者..." + this. name);
} finally{
lock.unlock();
}
}
}
class Producer implements Runnable{
private Resource r ;
Producer(Resource r){
this.r = r;
}
public void run(){
while(true ){
r.set( "烤鸭");
}
}
}
class Consumer implements Runnable{
private Resource r ;
Consumer(Resource r){
this.r = r;
}
public void run(){
while(true ){
r.out();
}
}
}
class ProducerConsumerDemo {
public static void main(String[] args){
Resource r = new Resource();
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
Thread t0 = new Thread(pro);
Thread t1 = new Thread(pro);
Thread t2 = new Thread(con);
Thread t3 = new Thread(con);
t0.start();
t1.start();
t2.start();
t3.start();
}
}
线程的停止:
线程执行完毕后需要停止线程,停止线程一般使用判断条件的方法
任务中都会有循环结构,只要控制住循环就可以结束任务。
控制循环通常就用定义标记来完成。
class StopThread implements Runnable{
private boolean flag = true;
public void run(){
//while方法读到flase标记时退出循环,这时run方法中所有代码执行完毕,线程会自动停止
while(flag ){
System. out.println(Thread.currentThread().getName() + "...");
}
}
public void setFlag(){
flag = false ;
}
}
class StopThreadDemo{
public static void main(String[] args){
StopThread st = new StopThread();
Thread t1 = new Thread(st);
Thread t2 = new Thread(st);
t1.start();
t2.start();
int num = 1;
for(;;){
if(++num == 50){
//当num==50时,把标记置换为假
st.setFlag();
break;
}
System. out.println("main..." + num);
}
System. out.println("over" );
}
}
但是如果线程处于了冻结状态,无法读取标记,如何结束呢?
可以使用interrupt()方法将线程从冻结状态强制恢复到运行状态中来,让线程具备CPU的执行资格。强制动作会发生InterruptedException,一定要记得处理。
class StopThread implements Runnable{
private boolean flag = true;
public synchronized void run(){
while(flag){
try{
//把线程放入等待池
wait();
}
//mian线程使用interrupt()方法强制将该线程恢复到运行状态,因此抛出了InterruptedException异常
catch(InterruptedException e){
System.out.println(Thread.currentThread().getName() + "..." + e);
//这时将标记置换为假,退出while循环,线程停止
flag = false;
}
System.out.println(Thread.currentThread().getName() + "......");
}
}
public void setFlag(){
flag = false;
}
}
class StopThreadDemo{
public static void main(String[] args){
StopThread st = new StopThread();
Thread t1 = new Thread(st);
Thread t2 = new Thread(st);
t1.start();
t2.start();
int num = 1;
for(;;){
if(++num == 50){
//num==50时,强制唤醒线程
t1.interrupt();
t2.interrupt();
break;
}
System.out.println( "main..." + num);
}
System.out.println( "over");
}
}
线程类的其他方法
setDaemon():
即后台线程,setDaemon()把线程改成守护线程,这个操作必须在线程启动前调用。守护线程跟一般线程没什么区别,只有一点,当当前运行的所有的线程都是守护线程时,守护线程会自动退出。
Join():
申请当前线程的执行权,当前线程会等待申请线程执行完再执行
setPriority():
设置优先级,优先级priority:1-10,默认优先级是5,优先级大的获取cpu执行权的可能性要大一点。参数一般使用Thread.MAX_PRIORITY(即10),Thread.MIN_PRIORITY(即1),Thread.NORM_PRIORITY(即5),
String toString()
返回该线程的字符串表示形式,包括线程名称、优先级和线程组。
static void yield()
暂停当前正在执行的线程对象,并执行其他线程。