本章结构图:
基本概念:
进程:是一个正在执行中的程序,
每一个进程执行都有一个执行顺序,该顺序是一个执行路径或者叫一个控制单元线程:就是进程中的一个独立的控制单元,线程控制着进程的执行。
一个进程中至少存在一个线程。
例如: Java VM 启动的时候会有一个进程java.exe。该进程中至少一个线程负责java程序的执行。而且这个线程运行的代码在于main方法中。该线程称为主线程。
扩展:其实更细节说明jvm,jvm启动不止一个线程,还有负责垃圾回收机制的线程。
线程的五种状态:
1、被创建2、运行(start)
3、阻塞(具备执行资格,但是没有获得cpu执行权)
4、冻结(sleep,wait,放弃了执行资格)
5、消亡
创建方式:
方法一:继承Thread类
1、定义类继承Thread2、复写Thread类中的run方法。
目的:将自定义代码存储在run方法中,调用run方法。
3、调用线程的start方法。
该方法有两个作用:启动线程,调用run方法。
4、代码如下:
public class ThreadDemo {
public static void main(String[] args) {
// TODO Auto-generated method stub
MyThread m = new MyThread();
m.start();
for(int i=0;i<100;i++){
System.out.println(Thread.currentThread().getName()+"...."+"主线程");
}
}
}
class MyThread extends Thread{
public void run(){
for(int i=0;i<100;i++){
System.out.println(Thread.currentThread().getName()+"......"+"子线程");
}
}
}<span style="white-space:pre"> </span>
多线程的一个特性:随机性。
运行多线程的程序可以看到,每次的结果都不一样,因为多个线程都获取cpu的执行权,cpu执行到谁,谁就运行。对于单核的cpu而言,在某一个时刻,只能有一个程序在运行。
cpu在做着快速的切换,以达到看上去是同时运行的效果
我们可以形象的把多线程的运行行为比喻成在互相抢夺cpu的执行权
为什么要覆盖run方法?
thread类用于描述线程。该类就定义了一个功能,用于存储线程要运行的代码,该存储功能就是run方法。
也就是说Thread类中的run方法,用于存储线程要运行的代码。
方法二:实现Runnable接口
步骤:1、定义类实现Runnable接口
2、覆盖Runnable接口中的run方法。
将线程要运行的代码存放在run方法中。
3、通过Thread类建立线程对象。
4、将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。
为什么要将Runnable接口的子类对象传递给Thread的构造函数。
因为。自定义的run方法所属的对象是Runnable接口的子类对象。
所以让线程去指定指定对象的run方法。就必须明确该run方法所属对象。
5、调用Thread类的start方法开启线程并调用Runnable接口子类的run方法。
6、代码如下:
public class TicketDemo {
public static void main(String[] args) {
Ticket t = new Ticket();
Thread th1 = new Thread(t);
Thread th2 = new Thread(t);
Thread th3 = new Thread(t);
Thread th4 = new Thread(t);
th1.start();
th2.start();
th3.start();
th4.start();
}
}
class Ticket implements Runnable {
private int res = 100;
Object o = new Object();
public void run(){
while(true){
synchronized(o){
if(res > 0){
//try{Thread.sleep(10);}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"...."+res--);
}
}
}
}
}
实现方式和继承方式有什么区别呢?
实现方式的好处:避免了单继承的极限性。在定义线程时,建议使用实现方式。
两种方法的区别:
继承Thread类:线程代码存放在Thread子类run方法中
实现Runnable:线程代码存放在接口子类的run方法中
同步(解决安全问题)
多线程的安全问题:
问题原因:当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,
还没有执行完,另一个进程参与进来执行,导致共享数据的错误。
解决办法:
对多条操作共享数据的语句,只能让一个线程都执行完。在执行过程中,
其他线程不可以参与执行。
实现方法:
1、同步代码块。
synchronized(对象){
需要被同步的代码。
}
对象如同锁,持有锁的线程可以在同步中执行。
没有持有锁的线程技术获取cpu的执行权,也进不去,因为没有获取锁。
火车上的卫生间————经典
2、同步函数使用的锁:this锁
如果函数中全是操作共享资源的,那么可以直接在函数上加synchronized关键字代码如下:
class Ticket implements Runnable
{
private int tick = 100;
Object obj = new Object();
boolean flag = true;
public void run()
{
if(flag)
{
while(true)
{
synchronized(this)
{
if(tick>0)
{
try{Thread.sleep(10);}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"....code : "+ tick--);
}
}
}
}
else
while(true)
show();
}
public synchronized void show()//this
{
if(tick>0)
{
try{Thread.sleep(10);}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"....show.... : "+ tick--);
}
}
}
class ThisLockDemo
{
public static void main(String[] args)
{
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t1.start();
try{Thread.sleep(10);}catch(Exception e){}
t.flag = false;
t2.start();
// Thread t3 = new Thread(t);
// Thread t4 = new Thread(t);
// t3.start();
// t4.start();
}
}
3、静态函数的锁:该方法所在类的字节码文件对象 类名.class
静态进内存时,内存中没有本类对象,但是一定有该类对应的字节码文件对象。类名.class 该对象的类型是class
代码如下:
class Ticket implements Runnable
{
private static int tick = 100;
//Object obj = new Object();
boolean flag = true;
public void run()
{
if(flag)
{
while(true)
{
synchronized(Ticket.class)
{
if(tick>0)
{
try{Thread.sleep(10);}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"....code : "+ tick--);
}
}
}
}
else
while(true)
show();
}
public static synchronized void show()
{
if(tick>0)
{
try{Thread.sleep(10);}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"....show.... : "+ tick--);
}
}
}
class StaticMethodDemo
{
public static void main(String[] args)
{
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t1.start();
try{Thread.sleep(10);}catch(Exception e){}
t.flag = false;
t2.start();
}
}
同步的前提:
1、必须有两个或者两个以上的线程。2、必须是多个线程使用同一个锁。
好处:解决了多线程的安全问题。
弊端:多个线程需要判断锁,较为消耗资源。
注意:如果加了同步还会出现安全问题那么一定是没有满足同步的两个前提。
小例子:
需求:银行有一个金库,两个用户向里面存钱,每人存三次,每次100.
关于同步的问题:
1、明确那些代码是多线程运行代码
2、明确共享数据
3、明确多线程运行代码中那些语句是操作共享数据的。
4、实现代码
public class BankDemo {
public static void main(String[] args) {
// TODO Auto-generated method stub
Bank b = new Bank();
Consumer c = new Consumer(b);
Thread t1 = new Thread(c);
Thread t2 = new Thread(c);
t1.start();
t2.start();
}
}
class Bank{
int sum = 0;
public synchronized void add(int num){
sum = sum+num;
try{
Thread.sleep(10);;
}
catch(Exception e){
System.out.println("chucuole");
}
System.out.println(Thread.currentThread().getName()+"..."+sum);
}
}
class Consumer implements Runnable{
Bank b;
public Consumer(Bank b){
this.b = b;
}
public void run(){
for(int i=0;i<3;i++){
b.add(100);
}
}
}
死锁:
嵌套同步中使用了两个锁,锁之间互相抢夺导致程序挂住不动。
例如:public class DeadLockDemo {
public static void main(String[] args) {
// TODO Auto-generated method stub
TextDemo d = new TextDemo(true);
TextDemo d1 = new TextDemo(false);
Thread t = new Thread(d,"么么哒你妹");
Thread t1 = new Thread(d1,"么么哒");
t.start();
t1.start();
}
}
// 死锁程序,无法继续运行
class TextDemo implements Runnable {
boolean flag;
TextDemo(boolean flag) {
this.flag = flag;
}
public void run() {
if (flag) {
while (true) {
synchronized (Text.locka) {
System.out.println(Thread.currentThread().getName()+"if locka ");
synchronized (Text.lockb) {
System.out.println("if lockb");
}
}
}
} else {
while (true) {
synchronized (Text.lockb) {
System.out.println("if locka ");
synchronized (Text.locka) {
System.out.println("if lockb");
}
}
}
}
}
}
//注意: Thread 中有静态方法currentThread()方法,该方法返回当前线程对象引用;
//getName()方法返回当前对象名称;
class Text {
public static Object locka = new Object();
public static Object lockb = new Object();
}
单例设计模式
饿汉式:
class Single{
private static Single s = new Single();
private Single(){}
public static void getInstence(){
return s;
}
}
懒汉式:
class Single{
private static Single s = null;
private Single(){}
public static void getInstence(){
if(s == null){
synchronized(Single.class){
if(s == null){
s = new Single();
}
}
}
return s;
}
}
懒汉式优点:
1、实例的延迟加载
2、问题:多线程访问时会出现安全问题,可以加同步来解决同步函数解决:低效
同步代码块解决:低效(但是加上双重判断可以提高效率)
3、加同步时候,使用的锁是该类所属的字节码文件对象。
*****************************************************
* 以上为自定义线程的写法以及安全问题,这个不是很 *
* 难,真正需要重点掌握的是线程建的通信问题,见下面 *
*****************************************************
线程之间的通信
线程之间的通信,实际上就是多个线程操作同一个资源,但是操作的动作不同 。
可以看出,线程之间的通信描述的是两个run方法中运行的是不同的代码,而之前买票的例子两个run方法中运行的是同一部分代码。
1、两个线程
public class InputOutputDemo2 {
public static void main(String[] args){
Res res = new Res();
new Thread(new In(res)).start();
new Thread(new Out(res)).start();
}
}
class Res{
private String name;
private String sex;
private boolean flag = false;
public synchronized void set(String name,String sex){
if(flag){
try{
this.wait();
}
catch(Exception e){
}
}
this.name = name;
this.sex = sex;
flag = true;
this.notify();
}
public synchronized void out(){
if(!flag){
try{
this.wait();
}
catch(Exception e){
System.out.println("chucuo");
}
}
System.out.println(name+"'''''"+sex);
flag = false;
this.notify();
}
}
class In implements Runnable{
Res res;
int flag = 0;
public In(Res res){
this.res = res;
}
public void run(){
while(true){
if(flag == 0){
res.set("lili", "woman");
flag = 1;
}
else{
res.set("麦克", "男");
flag = 0;
}
}
}
}
class Out implements Runnable{
Res res;
public Out(Res res){
this.res = res;
}
public void run(){
while(true){
res.out();
}
}
}
2、多个线程:
为了确保不出现安全问题,要用while循环,并定义notifyAll
用while循环原因:让被唤醒的线程再一次判断标记。定义notifyAll的原因:如果只用notify,容易出现只唤醒本方线程的情况,导致程序中的所有线程都等待。
3、jdk1.5中提供了多线程设计解决方案。
将同步synchronized替换成显示Lock操作
将Object中的wait、notify,notifyAll,替换成了Condition对象
可以实现本方只唤醒对方操作
Lock:替代了Synchronized
lock()
unlock()
newcondition()
Condition:替代了Object wait notify notifyAll
await();
signal();
signalAll();
代码如下:
class ProducerConsumerDemo2
{
public static void main(String[] args)
{
Resource r = new Resource();
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
Thread t1 = new Thread(pro);
Thread t2 = new Thread(pro);
Thread t3 = new Thread(con);
Thread t4 = new Thread(con);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class Resource
{
private String name;
private int count = 1;
private boolean flag = false;
// t1 t2
private Lock lock = new ReentrantLock();
private Condition condition_pro = lock.newCondition();
private Condition condition_con = lock.newCondition();
public void set(String name)throws InterruptedException
{
lock.lock();
try
{
while(flag)
condition_pro.await();//t1,t2
this.name = name+"--"+count++;
System.out.println(Thread.currentThread().getName()+"...生产者.."+this.name);
flag = true;
condition_con.signal();
}
finally
{
lock.unlock();//释放锁的动作一定要执行。
}
}
// t3 t4
public void out()throws InterruptedException
{
lock.lock();
try
{
while(!flag)
condition_con.await();
System.out.println(Thread.currentThread().getName()+"...消费者........."+this.name);
flag = false;
condition_pro.signal();
}
finally
{
lock.unlock();
}
}
}
class Producer implements Runnable
{
private Resource res;
Producer(Resource res)
{
this.res = res;
}
public void run()
{
while(true)
{
try
{
res.set("+商品+");
}
catch (InterruptedException e)
{
}
}
}
}
class Consumer implements Runnable
{
private Resource res;
Consumer(Resource res)
{
this.res = res;
}
public void run()
{
while(true)
{
try
{
res.out();
}
catch (InterruptedException e)
{
}
}
}
}
线程停止:
停止线程的方法唯一的方法就是让run方法结束。开启多线程运行,运行代码通常是循环结构,只要控制循环,就可以让run方法结束,也就是线程结束。
特殊情况:当线程处于冻结状态,就不会读取到标记,那么线程就不会结束。当没有指定的方式让冻结的线程恢复到运行状态时,这时需要对冻结进行清除。强制让线程恢复到运行状态中来,这样就可以操作标记让线程结束。
Thread类提供的方法: interrupt();
常用的API
1、public void interrupt():中断线程
2、public final void join()
throws InterruptedException 等待该线程终止。
join 特点:当A线程执行到了B线程的join()方法时,A线程就会等待,等B线程执行完,才会执行
3、public final void setDaemon(boolean on) 将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。
该方法必须在启动线程前调用。
4、public final void setPriority(int newPriority) 更改线程的优先级。
5、public static void sleep(long millis)
throws InterruptedException 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。该线程不丢失任何监视器的所属权。
6、public static void yield() 暂停当前正在执行的线程对象,并执行其他线程。
开发时常用的方式
当主线程的有很多循环并且后面的循环不容易被执行到时候,可以将部分循环放在另外的线程中,开启多个线程,有利于提高代码运行效率。
可以选择用匿名内部类的形式。也可以封装在一个具体的类中。
class ThreadTest
{
public static void main(String[] args)
{
new Thread()
{
public void run()
{
for(int x=0; x<100; x++)
{
System.out.println(Thread.currentThread().getName()+"....."+x);
}
}
}.start();
for(int x=0; x<100; x++)
{
System.out.println(Thread.currentThread().getName()+"....."+x);
}
Runnable r = new Runnable()
{
public void run()
{
for(int x=0; x<100; x++)
{
System.out.println(Thread.currentThread().getName()+"....."+x);
}
}
};
new Thread(r).start();
}
}