1.概述
进程:是一个正在执行中的程序。
每一个进程都有一个执行顺序。该顺序是一个执行路径,或者叫一个控制单元
线程:是进程中的一个独立的控制单元。
线程控制着进程的执行。
一个进程中至少有一个进程。
JVM 启动时就会有一个进程java.exe
该进程中至少一个线程负责java程序的执行。而且这个线程运行的代码存在于main方法中。
该线程成为主线程
扩展:细节说明,jvm启动时不止一个线程,还有一个负责垃圾回收机制的线程。
2.创建线程的第一种方式
创建线程的第一种方式:继承Tread类
步骤:
定义一个类继承Thread;
复写Thread类中的run方法,目的是存储自定义的代码,让线程运行;
调用线程的start方法(该方法有两个作用:启动线程和调用run方法)
ClassDemo extends Thread{
public void run(){
for(int x=0;x<100;x++)
System.out.println("demorun--"+x);
}
}
ClassMainDemo{
public static void main(String[] args){
Demo d = new Demo();
//d.run();//直接调用run方法不会开启线程,而是在主线程中继续运行
d.start();
for(int i=0;i<100;i++)
System.out.println("mainrun----"+i);
}
}
发现运行结果每一次都不同。
因为多个线程都在获取CPU的执行权。CPU执行到谁,谁就运行
CPU在作者快速的切换,所以看上去像是同时运行
3.线程运行的状态
4.创建线程的第二种方式
(1)举例
先举一个售票的例子
class Tickets implements Runnable{
private int count = 100;//票的数量为100
public void run(){
if(count>0)
System.out.println(Thread.currentThread().getName+"---"+count--);
}
}
class Sell{
public static void main(String[] args){
Tickets t = new Tickets();
//开启两个线程来卖票
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t1.start();
t2.start();
}
}
以上使用的就是线程的第二种创建方式:实现Runnable接口
(2)步骤
①定义类实现Runnable接口
②覆盖Runnable接口中的run方法,将线程要运行的代码放到run方法中
③通过Thread类建立线程对象
④将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数
(3)两种方式比较
特别说明:实现方式和继承方式有什么区别
实现方式的好处:避免了单继承的局限性。
定义线程时,建议使用实现的方式。
两种方式的区别:
继承Thread:线程代码放在了Thread子类的run方法中
实现Runnable:线程代码存放在了接口的子类run方法中
5.多线程的安全问题
(1)同步代码块
上个买票的例子中如果改为
class Tickets implements Runnable{
private int count = 100;
public void run(){
if(count>0){
try{Thread.sleep(10);}catch(Exceptione){}
System.out.println(Thread.currentThread().getName+"---"+count--);
}
}
}
则可能会出现打印出0、-1、-2等错票。多线程的运行出现了安全问题
原因:
当多条语句在操作同一个线程共享数据时,一个线程对共享数据只执行了一部分,
另一个线程参与进来执行,导致共享数据的错误
解决办法:
对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行
JAVA对于多线程安全问题提供了专业的解决方式-----同步代码块
synchronized(对象){
需要被同步的代码
}
class Tickets implements Runnable{
private int count = 100;
Object obj = new Object();
public void run(){
synchronized(obj){
if(count>0){
try{Thread.sleep(10);}catch(Exceptione){}
System.out.println(Thread.currentThread().getName+"---"+count--);
}
}
}
}
上面的程序中obj对象如同锁。持有锁的线程可以再同步中执行。
没有持有锁的线程即使获取CPU的执行权,也进不去,因为没有获取锁。
类似于火车上的卫生间
同步的前提:
①必须要有两个或两个以上的线程。
②必须是多个线程使用同一个锁。
必须保证同步中中只有一个线程在运行。
同步的好处:解决了多线程安全问题
同步的弊端:多个线程需要判断锁,较为消耗资源。
(2)同步函数
在函数上加同步
class Tickets implements Runnable{
private int count = 100;
public void run(){
sell();
}
public synchronized void sell(){
if(count>0){
try{Thread.sleep(10);}catch(Exceptione){}
System.out.println(Thread.currentThread().getName+"---"+count--);
}
}
}
同步函数使用的锁是this,谁调用它,就拿谁的对象当锁。
静态同步方法使用的锁是该方法所在类的字节码文件对象。类名.class
(3)单例模式—懒汉式
延迟加载,多线程访问会出现问题,需要加锁,为了提高点效率,可以在锁外面再加一个判断,锁是该类的字节码对象
public class Single {
private static Single s = null;
private Single(){}
public static SinglegetInstance(){
if(s==null){
synchronized(Single.class){
if(s==null)
s = new Single();
}
}
return s;
}
}
(4)死锁示例
class Test extends Thread{
boolean b;
public Test(boolean b){
this.b = b;
}
//同步代码块相互嵌套,就容易发生死锁
public void run(){
if(b){
while(true){
synchronized(DeadLock.class){
System.out.println("DeadLock.class...");
synchronized(Test.class){
System.out.println("...Test.class");
}
}
}
}else{
while(true){
synchronized(Test.class){
System.out.println("...Test.class");
synchronized(DeadLock.class){
System.out.println("DeadLock.class...");
}
}
}
}
}
}
6.线程间的通信
(1)生产者--消费者案例
说明:①对于多个生产者和消费者,为什么要用while标记?原因是让被唤醒的线程再判断一次标记
②为什么定义notifyAll,因为只用notify,容易出现只唤醒本方线程的情况。导致程序中的所有线程都等待
public class PCDemo {
public static void main(String[] args) {
Resource r = new Resource();//新建资源
//开启四个线程,两个生产,两个消费
new Thread(new Producer(r)).start();
new Thread(new Producer(r)).start();
new Thread(new Consumer(r)).start();
new Thread(new Consumer(r)).start();
}
}
//定义资源类
class Resource {
private int count = 0;
private boolean flag = false;
//资源类中的生产资源的方法
public synchronized void set(){
while(flag)
try{wait();}catch(Exception e){}
count++;
System.out.println(Thread.currentThread().getName()+"....生产商品.."+count);
this.flag = true;
this.notifyAll();
}
//资源类中消耗资源的方法
public synchronized void out(){
while(!flag)
try{wait();}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"..消费商品.."+count);
this.flag = false;
this.notifyAll();
}
}
//生产者类
class Producer implements Runnable{
private Resource r;
public Producer(Resource r){
this.r = r;
}
@Override
public void run() {
while(true)
r.set();
}
}
//消费者类
class Consumer implements Runnable{
private Resource r;
public Consumer(Resource r){
this.r = r;
}
@Override
public void run() {
while(true)
r.out();
}
}
(2)生产者--消费者案例—升级版
JDK1.5中提供了多线程升级解决方案。
将同步Synchronized替换成显式的Lock操作。
将Object中的wait,notify,notifyAll替换成了Condition对象。该对象可以用Lock锁进行获取。
该示例中实现了本方只唤醒对方的操作
package com;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class PCDemo2 {
public static void main(String[] args) {
Resource r = new Resource();
new Thread(new Producer(r)).start();
new Thread(new Producer(r)).start();
new Thread(new Consumer(r)).start();
new Thread(new Consumer(r)).start();
}
}
//定义资源类
class Resource {
private int count ;
private boolean flag = false;
//获取锁对象
Lock lock = new ReentrantLock();
//通过锁对象获取两个condition对象,分别对应生产者和消费者
Condition condition_pro = lock.newCondition();
Condition condition_con = lock.newCondition();
public void set() throws InterruptedException{
lock.lock();
try{
while(flag)
condition_pro.await();//生产者等待
count++;
System.out.println(Thread.currentThread().getName()+".....生产商品.."+count);
flag = true;
condition_con.signal();//唤醒消费者
}finally{
lock.unlock();
}
}
public void out() throws InterruptedException{
lock.lock();
try{
while(!flag)
condition_con.await();//消费者等待
System.out.println(Thread.currentThread().getName()+"..消费商品.."+count);
flag = false;
condition_pro.signal();//唤醒生产者
}finally{
lock.unlock();
}
}
}
//生产者类
class Producer implements Runnable{
private Resource r;
public Producer(Resource r){
this.r = r;
}
@Override
public void run() {
while(true){
try {
r.set();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//消费者类
class Consumer implements Runnable{
private Resource r;
public Consumer(Resource r){
this.r = r;
}
@Override
public void run() {
while(true){
try {
r.out();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
7.停止线程
stop方法已经过时了。那么如何停止线程呢?只有一种,就是run方法结束。开启多线程运行,运行代码通常是循环结构。只要控制住循环,就可以让run方法结束,也就是线程结束。
特殊情况:当线程处于冻结状态,就不会被读取到结束标记,线程就不会结束。
当没有指定的方式让冻结的线程恢复到运行中来时,这是需要对冻结进行清除。强制让线程恢复到运行中的状态来。这样就可以操作标记让线程结束。
Thread类提供了该方法interrupt(),可以让冻结状态的线程恢复到运行状态,但是要抛出InterruptedException。
8.守护线程
用线程的setDeamon(booleanb)方法可以将线程标记为守护线程,类似于后台线程,当前台的线程全部结束时,该线程自动结束,java虚拟机退出,程序结束。
该方法必须在线程启动前调用。
9.join方法
当A线程执行到了B线程的join()方法时,A就会等待。等待B线程都执行完,A才会执行。jion可以用来临时加入线程。
10.线程的优先级和yield方法
线程的优先级分为1-10级,默认为5。
yield方法用来临时定制正在运行的线程。