一、线程的状态
.(1)、阻塞状态:具备运行资格,但是没有执行权,必须等到cpu的执行权,才转到运行状态,。
(2)、冻结状态:放弃了cpu的执行资格,cpu不会将执行权分配给这个状态下的线程,必须被唤醒后,此线程要先转换到阻塞状态,等待cpu的执行权后,才有机会被执行到。
二、线程的创建
1、第一种方法:将类声明为 Thread 的子类。该子类应重写 Thread 类的 run 方法。接下来可以分配并启动该子类的实例
(1)、定义类继承 Thread
(2)、复写 Thread 类中的 run 方法
目的:将自定义代码存储在 run 方法周中,让线程运行
(3)、调用线程的 start 方法,该方法有两个作用:启用线程,调用 run 方法
2、 第二种方法: 实现Runnable接口 (1)、定义类实现 Runnable接口
(2)、覆盖 Runnable 接口中的 run 方法,将线程要运行的代码存放在该 run 方法中
(3)、通过 Thread 类建立线程对象
(4)、将 Runnable 接口的子类对象作为实际参数传递给 Thread 类的构造函数
[1]、为什么要将 Runnable 接口的子类对象传递给 Thread 的构造函数,因为自定义的 run 方法所属的对象是 Runnable 接口的子类对象,所以要让线程去指定对象的 run 方法
(5)、调用 Thread 类的 start 方法开启线程并调用 Runnable 接口子类的 run 方法
3、两种方式的区别:
(1)、实现方式好处:避免了单继承的局限性
(2)、在定义线程时:建议使用实现方式
(3)、两种方式区别:
[1]、继承 Thread:线程代码存放在 Thread 子类 run 方法中
[2]、实现 Runnable:线程代码存放在接口的子类的 run 方法中
4、示例:
package Demo;
public class CThreadDemo {
/**
* @线程的创建
* 1、利用两种方式获取当前线程名称
* 2、利用父类构造方法完成的自定义名称,实现自定义线程名称
*/
public static void main(String[] args) {
/*
* 方法一、用set方法自定义线程名称
*/
Person p=new Person();
p.setName("One");
// 方法二、父类完成了自定义线程名称的方法,调用即可
//Person p=new Person("Thread_one");
p.start();
}
}
class Person extends Thread{
private String name;
/*
方法二、父类完成了自定义线程名称的方法,调用即可
*/
/*Person(String name){
super(name);
}*/
public void run(){
System.out.println(this.getName());//利用this关键字获取当前线程名称
System.out.println(Thread.currentThread().getName());//利用currentThread()方法获取当前线程名称
}
}
三、获取线程对象及名称
1、线程都有自己默认的名称:Thread-编号 ,该编号从0开始
2、currentThread():返回对当前正在执行的线程对象的引用。
3、利用this关键字获取当前线程引用
4、getName():获取线程名称5、setName() 或者 构造函数:设置线程的名称
6、使用java自带的自定义线程名称方法
四、多线程安全问题-同步代码块和同步函数
1、对多条操作共享数据的语句,只能让一个线程都执行完,再执行过程中,不可以参与执行。
2、java对于多线程的安全,提供了专业解决方式:
(1)、就是同步代码块
(2)、synchronized(){}
3、同步代码块:
(1)、写同步的前提:
[1]、必须要有两个或者两个以上的线程。
[2]、必须是多个线程使用同一个锁。
(2)同步的好处:解决了多线程的安全问题;
(3)弊端:多个线程需要判断锁,较为消耗资源
(4)、同步代码块:synchronized(对象的引用),用在只让一个线程执行的多线程中。
synchronized (对象的引用)
{
需要被同步的代码。
}
持有锁的线程可以在同步代码块中执行,没有持有锁的线程即使获取cpu的执行权,也进不去。要等到持有锁的线程释放锁的所有权后,才可以让别的线程执行被同步的代码!
4、同步函数:同步函数使用的锁是this对象;当同步函数被静态static 修饰时,锁不再是this而是该方法所在类的字节码对象,即类 名.class。因为静态进内存的时候没有对象,只有一个字节码对 象,静态中不能有this对象。
5、示例:
public class Thread_Ticket {
/**
* @简单的多窗口卖票程序
*/
public static void main(String[] args) {
Ticket t=new Ticket();
Thread t1=new Thread(t);
Thread t2=new Thread(t);
Thread t3=new Thread(t);
Thread t4=new Thread(t);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class Ticket implements Runnable{
private int ticket=100;
Object obj=new Object();
@Override
public void run() {
while(true){
synchronized(obj){//同步代码块
if(ticket>0){
System.out.println(Thread.currentThread().getName()+"----"+ticket--);
}
}
}
}
}
五、多线程的锁锁对应的对象
1、同步函数使用的锁是this对象
2、当同步函数被静态static修饰时,锁不再是this而是该方法所在类的字节码对象,即类名.class
六、多线程-单利模式
1、饿汉式:饿汉式不会存在安全隐患,并且代码简单,开发常用。
class Single
{
private static final Single s = new Single();
private Single(){}
public static Single getInstance()
{
return s;
}
}
2、懒汉式:
class Single
{
private static Single s = null;
private Single(){}
public static Single getInstance()
{
if(s==null)
s = new Single();
return s;
}
}
多线程在执行上述语句时,由于cpu切换执行的特点,有可能某个线程执行了if(s==null)之后被挂起,这时cpu切换到另一个线程执行,最终会创建两个对象。从而不能实现最初目的。
懒汉式优化1:
class Single
{
private static Single s = null;
private Single(){}
public static Single getInstance()
{
synchronized(Single.class)
{
if(s==null)
s = new Single();
}
return s;
}
}
优化后的懒汉式,虽然解决了多线程的安全问题,但是每一个线程执行时都要判断“锁”,这是很浪费时间的。
懒汉式优化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;
}
}
这时 懒汉式不再需要多次判断“锁”,而是改为判断一条语句(s==null), 整个过程只需要判断一次锁,一旦A线程建立对象后,s便不等于null,此后的线程只需要判断if语句就知道对象已建立,便不再进入内部判断“锁”,节省了资源和时间。
七、多线程-等待唤醒机制
1、显式锁机制和等待唤醒机制:
在JDK 1.5中,提供了改进synchronized的升级解决方案。将同步synchronized替换为显式的Lock操作,将Object中的wait,notify,notifyAll替换成Condition对象,该对象可对Lock锁进行获取。这就实现了本方唤醒对方的操作。在这里说明几点:
1)、对于wait,notify和notifyAll这些方法都是用在同步中,也就是等待唤醒机制,这是因为要对持有监视器(锁)的线程操作。所以要使用在同步中,因为只有同步才具有锁。
2)、而这些方法都定义在Object中,是因为这些方法操作同步中的线程时,都必须表示自己所操作的线程的锁,就是说,等待和唤醒的必须是同一把锁。不可对不同锁中的线程进行唤醒。所以这就使得程序是不良的,因此,通过对锁机制的改良,使得程序得到优化。
3)、等待唤醒机制中,等待的线程处于冻结状态,是被放在线程池中,线程池中的线程已经放弃了执行资格,需要被唤醒后,才有被执行的资格。
2、对于上面的程序,有两点要说明:
1)、为何定义while判断标记:
原因是让被唤醒的线程再判断一次。避免未经判断,线程不知是否应该执行,就执行本方的上一个已经执行的语句。如果用if,消费者在等着,两个生成着一起判断完flag后,cpu切换到其中一个如t1,另一个t3在wait,当t1唤醒冻结中的一个,是t3(因为它先被冻结的,就会先被唤醒),所以t3未经判断,又生产了一个。而没消费。
2)这里使用的是signal方法,而不是signalAll方法。是因为通过Condition的两个对象,分别唤醒对方,这就体现了Lock锁机制的灵活性。可以通过Contidition对象调用Lock接口中的方法,就可以保证多线程间通信的流畅性了。
八、多线程间的通信:
/*
线程间通信:
生产者消费者 多个
*/
import java.util.concurrent.locks.*;
class ProducerConsumerDemo{
public static void main(String[] args){
Resouse r = new Resouse();
Producer p = new Producer(r);
Consumer c = new Consumer(r);
Thread t1 = new Thread(p);
Thread t2 = new Thread(c);
Thread t3 = new Thread(p);
Thread t4 = new Thread(c);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class Resouse{
private String name;
private int count = 1;
private boolean flag = false;
private Lock lock = new ReentrantLock();
private Condition condition_P = lock.newCondition();
private Condition condition_C = lock.newCondition();
//要唤醒全部,否则都可能处于冻结状态,那么程序就会停止。这和死锁有区别的。
public void set(String name)throws InterruptedException{
lock.lock();
try{
while(flag)//循环判断,防止都冻结状态
condition_P.await();
this.name = name + "--" + count++;
System.out.println(Thread.currentThread().getName() + "..生成者--" + this.name);
flag = true;
condition_C.signal();
}finally{
lock.unlock();//释放锁的机制一定要执行
}
}
public void out()throws InterruptedException{
lock.lock();
try{
while(!flag)//循环判断,防止都冻结状态
condition_C.await();
System.out.println(Thread.currentThread().getName() + "..消费者." + this.name);
flag = false;
condition_P.signal();//唤醒全部
}finally{
lock.unlock();
}
}
}
class Producer implements Runnable{
private Resouse r;
Producer(Resouse r){
this.r = r;
}
public void run(){
while(true){
try{
r.set("--商品--");
}catch (InterruptedException e){}
}
}
}
class Consumer implements Runnable{
private Resouse r;
Consumer(Resouse r){
this.r = r;
}
public void run(){
while(true){
try{
r.out();
}catch (InterruptedException e){}
}
}
}
九、停止线程
1、stop方法已经过时。多线程的运行代码通常是循环结构。只要控制循环,就可以让run方法结束,也就是线程结束。
· 2、 当线程处于冻结状态,就不会读取到标记,那么线程就不会结束。当没有指定的方式让冻结的线程恢复到运行状态时,这时需要对冻结进行清除。强制让线程恢复到运行状态中来,这样就可以操作标记让线程结束。
3、示例:
class Demo{
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 = 0;
while(true){
if(num++ == 60){
st.changeFlag();
t1.interrupt();
t2.interrupt();
break;
}
System.out.println(Thread.currentThread().getName()+num);
}
}
}
class StopThread implements Runnable{
private boolean flag = true;
public void run(){
while(flag){
System.out.println(Thread.currentThread().getName()+"....run");
}
}
public void changeFlag(){
flag = false;
}
}
十、守护线程
注意: 如果主线程结束,则这个线程自动结束
十一、jion方法
作用:抢夺主线程行权,注意,只抢夺主线程
当a线程执行到了b线程的 .join() 方法时,a就会等待,等b线程都执行完,a才会执行
十二、优先级&yield方法
1、在Thread中,存在着1~10这十个执行级别,最高的是 MAX_PRIORITY 为10,最低是 MIN_PRIORITY 为1,默认优先级是 NORM_PRIORITY 为5
2、setPriority 设置优先级