一、概念
进程:是一个正在执行中的程序,每一个进程都有一个执行顺序。该顺序是一个执行路径,或者叫一个控制单元
线程:就是进程中一个独立的控制单元,线程控制这进程的执行,一个进程中至少有一个线程
如:javaVM启动的时候会有一个进程叫java.exe该进程中至少有一个线程在负责java程序的执行,而且这个线程运行的代码存在于main方法中,该线程称之为主线程。其实更严谨的来说,javaVM启动的时候不止一个线程,还有一个垃圾回收的线程
多线程:一个进程中有多个线程同时运行
线程运行状态:
创建:start()
运行:具备执行资格,同时具备执行权;
冻结:sleep(time),wait()—notify();释放执行资格;
临时阻塞状态:线程具备cpu的执行资格,没有cpu的执行权;
消亡:stop()
二、多线程的创建方式
第一种方式:自定义一个线程并继承Thread类。
1、定义类继承Thread。
2、复写Thread类中的run方法。
目的:将自定义代码存储在run方法。让线程运行。
3、调用线程的start方法,
该方法两个作用:启动线程,调用该线程的run方法。
为什么要覆盖run方法?
Thread类用于描述线程该类定义了一个功能,用于存储线程要运行的代码。该存储功能就是run方法也就是说Thread类中的run方法,用于存储线程要运行的代码。
主要方法:
代码示例:
class SubThread extends Thread {
public SubThread(String name) {
super(name);
}
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(this.getName()+"--thread");
//Thread.currentThread()==this
System.out.println(Thread.currentThread().getName()+"thread");
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
SubThread st = new SubThread("one");//创建一个线程,并命名one
st.start();//开启一个线程
for (int i = 0; i < 100; i++) {
System.out.println("hello");
}
}
}
第二种方式:实现Runnable接口
1、定义类实现Runnable接口
2、覆盖Runnable接口中的run方法。
将线程要运行的代码存放在该run方法中。
3、通过Thread类建立线程对象。
4、将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。
为什么要将Runnable接口的子类对象传递给Thread的构造函数。
因为,自定义的run方法所属的对象是Runnable接口的子类对象。
所以要让线程去指定指定对象的run方法。就必须明确该run方法所属对象。
5、调用Thread类的start方法开启线程并调用Runnable接口子类的run方法。
实现方式和继承方式有什么区别?
实现方式:避免了但继承的局限性 在定义线程时 建议使用此方式
两种方式的区别:
继承Thread:线程代码存放在thread子类run方法中
实现Runnable:线程代码存放在接口子类run方法中
示例代码:卖票小程序
class Ticket implements Runnable{//extends Thread{
private int tic=100;
public void run(){
while(true){
if(tic>0){
System.out.println(Thread.currentThread().getName()+"ticket"+tic--);
}
}
}
}
public class TicketDemo {
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();
}
}
分析:在上述卖票程序中,如果我们按照继承Thread的方式来创建线程 那么假如有四个机器卖票,则我们需要创建四个Ticket对象,每一个对象对应一个线程
Ticket t1=new Ticket();
Ticket t2=new Ticket();
Ticket t3=new Ticket();
Ticket t4=new Ticket();
t1.start();
t2.start();
t3.start();
t4.start();
这样一来,每一个对象就会对应100张票,就会卖出400张票,这显然是不行的。而通过实现Runnable接口我们只需要创建一个Ticket对象,让它开启四个线程。这样四个线程就会共享这100张票,符合我们的需求
三、线程同步
上述例子存在问题:当0线程进入if循环之后还没有执行tic--,cpu将其执行权切换到了1线程,可能1线程进到循环之后也没有执行tic--,其执行权又被cpu切换到了线程2。我们可以在tic--该行代码前加上Thread.sleep(10),来模拟这一现象 ,结果会打印出-1,-2号票 这就是程序存在的安全隐患。
原因在于:当多条语句在操作线程共享数据时候,一个线程对多条语句值执行了一部分还没有至执行完,另一个线程进来执行,导致共享数据错误
解决办法:对多条操作共享数据的语句,只能让一个线程执行完,在执行过程中其他线程不能进来执行,Java对于多线程的安全问题提供了专业的处理办法。就是同步代码块:
synchronized(对象)
{
需要被同步的代码
}
对象如同锁,想持有所锁的线程可以同步进行没有锁的进程即使持有cpu的执行权也进不去,因为没有获取锁
同步的前提:
1、必须有两个或两个以上的线程
2、必须是多个线程使用同一个锁
好处:解决了多线程的安全问题
弊端:多个线程需要判断锁,比较消耗资源
上述代码可修改为:
class Ticket implements Runnable{//extends Thread{
private int tic=100;
Object obj=new Object();
public void run(){
while(true){
synchronized (obj) {
if(tic>0){
try {
Thread.sleep(10);//出现-1、-2号票 有安全问题
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"ticket"+tic--);
}
}
}
}
}
同步函数:让函数具备同步性;
同步函数用的是哪一个锁呢?
函数需要被对象调用。那么函数都有一个所属对象引用。就是this。
所以同步函数使用的锁是this。
通过程序来验证:
一个线程在同步代码块中
一个线程在同步函数中
两个线程都在执行买票动作,如果在在同步代码块中使用obj锁就会出现0号票,如果使用this就不会出现
class Ticket2 implements Runnable {// extends Thread{
private int tic = 100;
Object obj = new Object();
boolean flag = true;
public void run() {
if (flag) {
while (true) {
synchronized (this) {
if (tic > 0) {
try {
Thread.sleep(10);// 出现-1、-2号票 有安全问题
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "obj::" + tic--);
}
}
}
} else
while (true)
show();
}
public synchronized void show() {
if (tic > 0) {
try {
Thread.sleep(10);
} catch (Exception e) {
}
System.out.println(Thread.currentThread().getName() + "....show.... : " + tic--);
}
}
}
public class ThisLockDemo {
public static void main(String[] args) {
Ticket2 t = new Ticket2();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t1.start();
try {
Thread.sleep(10);
} catch (Exception e) {
}
t.flag = false;
t2.start();
}
}
注意:如果同步函数被static修饰后,使用的锁是该类对应的字节码文件对象,就是类名.class,因为静态随着类的加载而加载
死锁:同步当中嵌套着同步。而锁却不同
class DeadTest implements Runnable {
private boolean flag;
DeadTest(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
if (flag) {
synchronized (MyLock.lacka) {
System.out.println("if...locka");
synchronized (MyLock.lockb) {
System.out.println("if....lockb");
}
}
} else {
synchronized (MyLock.lockb) {
System.out.println("else...lockb");
synchronized (MyLock.lacka) {
System.out.println("else....locka");
}
}
}
}
}
class MyLock {
public static Object lacka = new Object();
public static Object lockb = new Object();
}
public class LockDemo {
public static void main(String[] args) {
Thread t1 = new Thread(new DeadTest(true));
Thread t2 = new Thread(new DeadTest(false));
t1.start();
t2.start();
}
}
四、线程间通信
线程间通讯:其实就是多个线程在操作同一个资源,但是操作的动作不同。
class Res {
String name;
String sex;
}
class Input implements Runnable {
private Res r;
Input(Res r) {
this.r = r;
}
@Override
public void run() {
int x = 0;
while (true) {
synchronized (r) {
if (x == 0) {
r.name = "mike";
r.sex = "man";
} else {
r.name = "丽丽";
r.sex = "女女女";
}
x = (x + 1) % 2;
}
}
}
}
class Output implements Runnable {
private Res r;
Output(Res r) {
this.r = r;
}
@Override
public void run() {
while (true) {
synchronized (r) {
System.out.println(r.name + ":::" + r.sex);
}
}
}
}
public class InputOutput {
public static void main(String[] args) {
Res r = new Res();
Input i = new Input(r);
Output o = new Output(r);
Thread t1 = new Thread(i);
Thread t2 = new Thread(o);
t1.start();
t2.start();
}
}
上述代码打印结果会出现连续的存储或者连续的取出,因此我们要加入线程的等待唤醒机制 让结果是存储一个,取出一个。上述代码可修改为
class Res {
String name;
String sex;
boolean flag = false;
}
class Input implements Runnable {
private Res r;
Input(Res r) {
this.r = r;
}
@Override
public void run() {
int x = 0;
while (true) {
synchronized (r) {
try {r.wait();} catch (Exception e) {}
if (x == 0) {
r.name = "mike";
r.sex = "man";
} else {
r.name = "丽丽";
r.sex = "女女女";
}
x = (x + 1) % 2;
r.flag = true;
r.notify();
}
}
}
}
class Output implements Runnable {
private Res r;
Output(Res r) {
this.r = r;
}
@Override
public void run() {
while (true) {
synchronized (r) {
if (!r.flag)
try {r.wait();} catch (Exception e) {}
System.out.println(r.name + ":::" + r.sex);
r.flag = false;
r.notify();
}
}
}
}
wait:线程处于等待状态。放弃了执行权,放弃了锁。
notify():唤醒线程池中 的第一个等待线程
notifyAll():唤醒线程池中的所有等待线程。
都使用在同步中,因为要对持有监视器(锁)的线程操作。
所以要使用在同步中,因为只有同步才具有锁。
为什么这些操作线程的方法要定义Object类中呢?
因为这些方法在操作同步中线程时,都必须要标识它们所操作线程只有的锁,
只有同一个锁上的被等待线程,可以被同一个锁上notify唤醒。
不可以对不同锁中的线程进行唤醒。
也就是说,等待和唤醒必须是同一个锁。
而锁可以是任意对象,所以可以被任意对象调用的方法定义Object类中。
生产者与消费者
class Resource{
private String name;
private int count=0;
private boolean flag=false;
public synchronized void set(String name){
while(flag)
try {this.wait();}catch(Exception e) {}
this.name=name+"..."+count++;
System.out.println(Thread.currentThread().getName()+"生产者"+this.name);
flag=true;
this.notifyAll();
}
public synchronized void get(){
while(!flag)
try {this.wait();}catch(Exception e) {}
System.out.println(Thread.currentThread().getName()+"消费者"+this.name);
flag=false;
this.notifyAll();
}
}
class Producer implements Runnable{
private Resource res;
Producer(Resource res){
this.res=res;
}
@Override
public void run() {
while(true){
res.set("商品");
}
}
}
class Consumer implements Runnable{
private Resource res;
Consumer(Resource res){
this.res=res;
}
@Override
public void run() {
while(true){
res.get();
}
}
}
public class ProducerConsumer {
public static void main(String[] args) {
Resource res = new Resource();
Producer pro = new Producer(res);
Consumer con = new Consumer(res);
Thread t1 = new Thread(pro);
Thread t2 = new Thread(con);
Thread t3 = new Thread(pro);
Thread t4 = new Thread(con);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
对于多个生产者和消费者。要使用while循环判断标记,让被唤醒的线程再一次判断标记。不能使用if循环
要使用notifyAll(唤醒所有线程),是为了唤醒对方线程。notify,可能出现只唤醒本方线程的情况。导致程序中的所有线程都等待。
Jdk1.5对多线程的操作进行了升级
将同步Synchronized替换成显示Lock操作。
将Object中的wait,notify notifyAll,替换了Condition对象。
该对象可以通过Lock锁 进行获取。
下面示例中,实现了本方只唤醒对方操作。
class Resource {
private String name;
private int count = 0;
private boolean flag = false;
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();
this.name = name + "..." + count++;
System.out.println(Thread.currentThread().getName() + "生产者" + this.name);
flag = true;
condition_con.signal();
} finally {
lock.unlock();
}
}
public void get() 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;
}
@Override
public void run() {
while (true) {
try {
res.set("商品");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Consumer implements Runnable {
private Resource res;
Consumer(Resource res) {
this.res = res;
}
@Override
public void run() {
while (true) {
try {
res.get();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class ProducerConsumer {
public static void main(String[] args) {
Resource res = new Resource();
Producer pro = new Producer(res);
Consumer con = new Consumer(res);
Thread t1 = new Thread(pro);
Thread t2 = new Thread(con);
Thread t3 = new Thread(pro);
Thread t4 = new Thread(con);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
五、线程的其他操作
1、停止线程
如何停止线程?
只有一种,run方法结束。
开启多线程运行,运行代码通常是循环结构。
只要控制住循环,就可以让run方法结束,也就是线程结束。
特殊情况:
当线程处于了冻结状态。
就不会读取到标记。那么线程就不会结束。
当没有指定的方式让冻结的线程恢复到运行状态时,这时需要对冻结进行清除。
强制让线程恢复到运行状态中来。这样就可以操作标记让线程结束。
Thread类提供该方法 interrupt();强制让冻结状态中的线程恢复到运行状态,而不是停止线程。它会获得一个中断异常。
public static final int NORM_PRIORITY
分配给线程的默认优先级。(5)
public static final int MAX_PRIORITY
线程可以具有的最高优先级。(10)