------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------
一 线程是啥?
每一个运行着得程序对应一个进程,是cup分配资源的最小单元。
而每一个进程都包含一个和多个线程,程序(进程)所完成的功能,实际是由一个个独立的线程进行完成的。
比如:
JVM启动的时候会有一个进程:java.exe该进程中至少有一个线程在负责java程序的执行,而且这个
线程运行的代码在于main方法中。该线程成为主线程。
二java中创建线程的方式
方式1:继承实现方式
1 定义一个Thread类的子类
2 复写run() 方法
目的:定义该线程所需要执行的代码。即定义该线程的功能
3 创建该类对象
4 该对象.start()
作用:让创建的线程处于可抢夺CUP的状态(临时状态或就绪状态)
方式2:实现方式
1 定义一个类实现Runnable接口
2 复写run()方法
3 用该类对象作为参数创建一个Thread对象: Thread t = new Thread(实现Runnable接口类的对象)
4 t.start();//让创建的线程处于可争夺CPU的状态
为啥2种方式都要复写run方法?
因为JAVA中定义Thread类来描述线程
1 该类定义了一个功能,用于存储线程要运行的代码。该存储功能就是run方法。
也就是说:thread类中的run方法,用于存储线程要运行的代码。
2 Thread类也实现了Runnable接口
实现方式和继承方式的区别?
实现方式的好处:避免了单继承的局限性,
在定义线程时,建议使用实现方式。
两种方式的区别:
继承Thread类:线程代码存放在Tread子类的run()方法中。
实现Runnabel接口:线程代码在接口子类的run()方法中。
线程的几种状态:
三 线程安全问题
为啥会出现安全问题?
原因:不同线程对线程共享数据进行访问的,多条代码可能只执行一部分之后就停止(CPU的切换)
java 为线程线程安全问题所提供的专业解决办法:
同步代码块:
synchronized(对象)
{
操作共享数据的代码
}
对象相当于锁,持有锁的线程可以再同步代码块中执行,没有锁的线程即使获得了cpu
的执行权,也进不去。保证了对共享数据操作的安全性。
保证多个线程同步的前提:
1 必须是2个或2个以的线程
2 必须保证是同一个锁。
目的: 保证了同步代码块儿中的代码只有一个线程在运行。
同步的缺点:多个线程需要进行锁的判断,较为消耗资源。
如何确定同步代码块儿所加的位置:
1 明确哪些代码是多线程运行的代码。-----Run()中的所有
2 明确共享数据。
3 明确多线程运行代码中的哪些语句是操作共享数据的。//同步代码块的使用地方
-------------------------------------------------------------
同步的第二种实现方式:同步函数
例如:
public synchronized void add(int n)
{
}
同步函数用的是哪一个锁?
函数需要被对象调用,那么函数都有一个所属的引用对象。就使用的锁是this。
所以函数是this
静态函数的同步锁是该方法所在类的字节码文件对象类名.Class;
因为静态函数不存在this引用
死锁:
死锁发生的原因:1 存在多个锁
2 同步中嵌套者同步
示例:
class Test implements Runnable
{
@Override
public void run() {
// TODO Auto-generated method stub
synchronized(Lock.locka)
{
System.out.println("Test locka");
synchronized(Lock.lockb)
{
System.out.println("Test lockb");
}
}
}
}
class Test2 implements Runnable
{
@Override
public void run() {
// TODO Auto-generated method stub
synchronized(Lock.lockb)
{
System.out.println("Lockb test2");
synchronized(Lock.locka)
{
<pre name="code" class="java"> System.out.println("locka test2");
}}}}class Lock{static Object locka = new Object();static Object lockb = new Object();}public class deadTreadDemo {public static void main(String[] args) {// TODO Auto-generated method stubTest t = new Test();Test2 t2 = new Test2();Thread th1 = new Thread(t);Thread th2 = new Thread(t2);th1.start();th2.start();}}
当 th1 线程获得cpu后执行到
System.out.println("Test locka");失去了CPU的执行权, 但并未释放同步锁对象locka
此时th2 线程获得cpu的执行全当执行到:
System.out.println("Lockb test2");失去了cpu的执行权,但并未释放同步锁对象lockb
此时th1再次获得cpu执行权,但并未获得lockb对象因而无法进入下一个同步块,无法执行
System.out.println("Test lockb");而无法结束线程释放同步锁locka
当一段时间后Th2线程在获得cpu执行权,但并未获得同步锁对象locka因为无法执行下一个同步块
无法执行
<pre name="code" class="java"> System.out.println("locka test2");
无法结束线程释放同步锁lockb
这样就导致2个线程再循环等待资源,死锁。
------------------------------------------------------------------------------------------
线程间的通信:
wait();
notify();
notifyAll();
以上方法都定义在Object类中;因为锁是任意对象,而线程的wait(),notify();notifAll()只是针对某个具体的锁而言的。实质是: 锁对象.wait();锁对象.notify();
通信:一个线程唤醒另一个线程
问题描述:当存在多个线程对某一共享资源进行操作时,线程之间的操作应当满足一定的顺序时(如:先输入才能输出)如何实现?
此时就要用到线程之间的通信机制。
实例:
生产者消费者问题
<pre name="code" class="java">//
class Resource
{
private boolean flag = false;//信号量:标志是否有商品
private String name;
private int num =0;
public void put(String name)throws InterruptedException//name 和num是共享数据
{
synchronized(this)//只是保证同步了 但还并未保证先生产后消费:不能保证生产一个消费一个交替运行
{
if(flag)//如果有商品 则生产停止
{
wait();//放弃CPU的执行权等待消费者的唤醒
}
this.name = name;
num++;
System.out.println(name+"----生产者-----"+num);
flage = true;//有货了
notify();//打电话给消费者说有货了
}
}
public void get()throws InterruptedException
{
synchronized(this)
{
if(!flag)//没有消费品
{
wait();//消费者等待
}
System.out.println(name+"----消费者-------"+num);
flag = false;//没货了
notify();//通知生产者没货了(唤醒生产线程)
}
}
}
class Producer implements Runnable
{
private Resource r;
public Producer(Resource r)
{
this.r = r;
}
public void run()
{
while(true)
{
try
{
r.put("商品");
}
catch(InterruptedException e)
{
}
}
}
}
class Consumer implements Runnable
{
<pre name="code" class="java"><pre name="code" class="java"> private Resource r;
public Consumer (Resource r)
{
this.r = r;
}
public void run()
{
while(true)
{
try
{ r.get();
}
catch(InterruptedException e )
{
}
}
}
}
class ProConDemo1
{
public static void main(String[]arg)
{
Resource r = new Resource();
Producer pro = new Producer (r);
Consumer con = new Consumer(r);
Thread t1 = new Thread(pro);
Thread t2 = new Thread(con);
t1.start();
t2.start();
}
}
新问题的产生:
当存在多个消费者线程和多个生产者线程时:
可能会出现:生产多个商品但值被消费一个;或者是生产一个商品被多个消费者消费的情况
产生原因:
1 当线程在if(){}块中wait()之后,当再次获得CPU执行权时不会再对flag进行判断;从而出现多次消费,或者多次生产的现象;
解决办法:将if(flag)改为:while(flag); 让线程始终对flag进行判断
2 改成while()之后出现新的问题:
所有线程都wait()了。让程序无法正常执行。
原因:notify();唤醒的是:先进入等待所队列中的线程,这样有可能唤醒的是己方(同为生产者线程或者同为消费者线程)线程;
解决办法:notifyAll();
具体分析:
<pre name="code" class="java"><img src="https://img-blog.csdn.net/20140822103745437?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdTAxMjE5MDQ5OQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" height="710" width="907" alt="" />
JDK1.5中提供新方法解决锁问题:
用实现Lock接口的类 代替synchronized,使用condtion 来进行wait(),notify() 等操作
lock的特点:可以为同一个lock设立多个condition对象,为不同线程分类:如将线程分为生产者线程类,消费者线程类。当线程间进行唤醒操作时可以对同一个lock的不同类线程进行唤醒。
实例:
<pre name="code" class="java"> import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/*JDK 1.5以及之后对锁的处理
* 1 用 Lock接口的实现类对象 替代 synchronized
* 2 用 condition 来进行wait(),notify(),notifyAll()操作
* 3 特点:
* 1 需显示的加锁 Lock lock.lock();
* 2 显示释放锁 lock.unlock();
* 3 一个锁可对应多个condition对象
* */
class Product1//拉煤优化进程间通信 优化代码
{
private String name;
private int num = 0;
private boolean flag = false;
private ReentrantLock lock = new ReentrantLock();
private Condition producer_con;
private Condition consumer_con ;
public Product1(String name)
{
this.name = name;
producer_con = lock.newCondition();
consumer_con = lock.newCondition();
}
//生产行为
public void put()throws InterruptedException
{
lock.lock();//显示调用加锁
try
{
while(this.flag)
{
producer_con.await();//可能出现异常,出现异常时必须释放锁
}
System.out.println(name+"---"+"生产者----"+(++num));//模拟生产者进行的操作处理
flag = true;
//this.notifyAll();//唤醒消费者
consumer_con.signal();
}finally
{
lock.unlock();//显示释放锁
}
}
//消费行为
public void get() throws InterruptedException
{
lock.lock();
try
{
while(flag == false)
{
consumer_con.await();
}
System.out.println(name+"---"+"消费者--------------"+num);
flag = false;
//this.notifyAll();//唤醒生产者
producer_con.signal();// 只唤醒 生产者线程不唤醒本方线程
}
finally
{
lock.unlock();
}
}
}
//生产者
class Producer1 implements Runnable
{
private Product1 product;
public Producer1(Product1 product)
{
this.product = product;
}
public void run()
{
while(true)
{
try{
product.put();
}
catch(InterruptedException e)
{
}
}
}
}
//消费者
class Consumer1 implements Runnable
{
private Product1 product;
public Consumer1(Product1 product)
{
this.product = product;
}
public void run()
{
while(true)
{
try
{
product.get();
}
catch(InterruptedException e)
{
}
}
}
}
public class producerConsumerDmeo2 {
public static void main(String[] args) {
// TODO Auto-generated method stub
Product1 product = new Product1("商品");
Producer1 producer = new Producer1(product);
Consumer1 consumer = new Consumer1(product);
Thread t1 = new Thread(producer);
Thread t2 = new Thread(producer);
Thread t3 = new Thread(consumer);
Thread t4 = new Thread(consumer);
t1.start();//问题的产生:当存在多个生产者,多个消费者时:一个商品被2个消费者消费
t2.start();
t3.start();
t4.start();
}
四 线程中的常用函数
线程停止的方法
stop():已过时
interrupt();
如何停止一个线程:
从本质上说:只有一个方法:结束Run方法。
当开启一个线程时:线程运行的通常是循环结构的代码,所以要结束run();只要控制住循环。
特殊情况:
当线程处于冻结状态。
就不会读取到标记。那么线程就不会结束
当没有指定的方式来让冻结的线程回复运行状态时,这时需要对冻结进行清除。
强制让线程回复到运行状态中来,这样就可以操作标记让线程结束。
interrupt:清除线程的冻结状态
调用一个线程的interrupt()方法时会触发InterruptedException 异常
在异常处理函数中修改循环条件。
setDaemo();
t1.setDaemon();让t1成为守护线程(后台线程);当前台线程(创建t1的线程)结束时t1会自动结束
注意:该方法必须在线程开启之前调用
join()方法
当A线程,执行到了B线程的.join()方法时,A就会等待B线程执行完
A才会执行。
join可以用来临时加入线程。
线程优先级
t.setPriority(int )//优先级 只有10级
1:MIN_PRRORITY
5:NORMAL_PROORITY
10:MAX_PRORITY
yield() 方法;
Thread.yield();//线程会释放执行权。类似sleep();