文章目录
线程
程序,进程,线程
程序:为完成某种特定的任务而有某种语言设定的一组指令集合。就是一段静态代码。
进程:正在运行的程序。Windows系统中,进程是操作系统进行资源分配的最小单位。
线程:进程的进一步细化,是一个进程内部的最小执行单元,是操作系统进行任务调度的最小 单元,属于进程。
例如:电脑里的QQ属于一个程序;当打开QQ后,就会进入进程序列;当打开多个窗口同时聊天时就产生多个线程。
线程和进程的关系
-
一个进程可以包含多个线程,一个线程只属于一个进程,线程不能脱离进程单独存在。(当QQ关闭时,所有的聊天窗口都会关闭)
-
每个进程至少包含一个线程,该线程称为主线程。在java中main()方法所在的线程就是组线程。
-
一个进程内所有的线程共享该进程的内存资源。
创建线程
创建一个线程在java中共有三种方法
- 继承Thread类的方式。
- 实现Runnable接口的方式。
- 实现Callable接口的方式。
继承Thread类的方式
该方法下创建线程的步骤:
- 写一个类继承Thread类;
- 在该类中重写run()方法;
- 在run()方法内写入执行代码;
- 在其他类中创建线程类对象;
- 利用对象调用start()方法开启线程。
public class MyThread extends Thread {//继承Thread类
//重写run方法
@Override
public void run() {
//线程内容
for (int i=0;i<1000;i++){
System.out.println("MyThread:"+i);
}
}
}
public class Test1 {
public static void main(String[] args) {
//创建线程对象
MyThread myThread = new MyThread();
//开启线程
myThread.start();
for (int i=0;i<1000;i++){
System.out.println("main:"+i);
}
}
}
实现Runnable接口
java只支持单继承,如果一个类已经继承一个其他类了之后就无法在继承Thread类来实现多线程。由此引进实现Runnable接口的方法。
该方法下创建线程的步骤:
- 写一个类实现Runnable接口;
- 在该类中重写run()方法;
- 在run()方法内写入执行代码;
- 在其他类中创建该类对象;
- 在创建Thread类的对象,并将该类的对象传入;
- 利用Thread类的对象调用start()方法开启线程。
public class MyThread2 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
//Thread.currentThread().getName()获取当前线程的名字.
System.out.println(Thread.currentThread().getName()+":" +i);
}
}
}
public class Test1 {
public static void main(String[] args) {
MyThread2 myThread2 = new MyThread2();
/*将实现类Runnable接口的对象传入到Thread对象中,并未该线程命名
Thread t = new Thread(myThread2,"自定义线程");*/
//将实现类Runnable接口的对象传入到Thread对象中
Thread t = new Thread(myThread2,"自定义线程");
t.start();
for (int i = 0; i < 1000; i++) {
System.out.println("main:"+i);
}
}
}
实现Callable接口的方式.
前两种创建的线程都run方法都没有返回值,实现Callable接口的方式与Runnable方式相比,ru方法可以有返回值,方法可以抛异常,支持泛型的返回值。
该方式的步骤:
- 自定义一个类实现Callable接口;
- 重写call()方法;
- 创建该类的对象;
- 借助FutureTask<> 对象来接收任务(该类对象);
- 创建Thread线程类对象,并将FutureTask<> 对象传入;
import java.util.concurrent.Callable;
//支持泛型的返回值
public class CallableDemo implements Callable<Integer> {
int sum;
@Override//重写call方法
public Integer call() throws Exception {
for (int i = 0; i < 10; i++) {
sum += i;
}
return sum;
}
}
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建实现Callable接口类的对象
CallableDemo callableDemo = new CallableDemo();
//借助FutureTask获取返回值
FutureTask<Integer> futureTask = new FutureTask(callableDemo);
//利用Thread开启线程
Thread t = new Thread(futureTask);
//开启线程
t.start();
//futureTask.get()获取call方法的返回值
System.out.println(futureTask.get());
}
}f
Thread类中的方法
Thread中会有一些线程相关的方法
start()开启线程;
setName(String name)设置线程的名称;
getName()获取线程名称;
setPriority(int newPriority) 设置线程的优先级;
getPriority()获得线程的优先级;
join()使线程处于等待状态(抛异常);
sleep(long millis)使线程进入休眠状态(单位为:毫秒,抛异常);
currentThread()返回当前正在执行线程的对象的引用。
public class Test1 {
public static void main(String[] args) throws InterruptedException {
MyThread2 myThread2 = new MyThread2();
Thread t = new Thread(myThread2,"自定义线程");
t.start();
// t.setName("自定义线程");
System.out.println(t.getName()+"的优先级:"+ t.getPriority());
t.setPriority(10);
System.out.println(t.getName()+"的优先级:"+ t.getPriority());
Thread.currentThread().setName("主线程");
System.out.println(Thread.currentThread().getName()+"的优先级:"+Thread.currentThread().getPriority());
//t.join();//将t线程加入到当前线程中来,当前线程进入阻塞状态,直到t线程死亡在继续执行当前线程
for (int i = 0; i < 1000; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
public class MyThread2 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
try {
Thread.sleep(10);//线程休眠 此时该线程将进入阻塞状态 ,休眠时间结束时会进入就绪状态
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread.yield();//线程让步, 执行后线程会失去CP进入就绪状态
System.out.println(Thread.currentThread().getName()+":" +i);
}
}
}
线程的优先级
计算机中只有一个CPU,各个线程轮流获得CPU的使用权,才能执行任务;
优先级越高的线程获得CPU的机会更多;
优先级用整数0~10表示,默认优先级为5,10位最高优先级;
setPriority(int newPriority) 设置线程的优先级;
getPriority()获得线程的优先级;
线程的状态
线程状态有:
新建:当一个Thread类或者其子类的对象被建立声明时,新的线程对象就处于新建状态;
就绪:处于新建状态的线程,其对象调用start() 方法后,将进入线程队列等待CPU时间片,此时该线程具有运行的条件,只是没有分配到CPU;
运行:当就绪状态的线程获得CPU时,就进入运行状态,此时会执行线程类中run()方法的操作;
阻塞:由某种特殊原因,运行线程被人为挂起或执行输入输出操作时,让出CPU并临时终止自己的执行,从而进入阻塞状态。
死亡:线程执行完它的全部任务或者被提前强制终止或者是出现异常而导致线程结束。
线程的生命周期:
线程的分类
java中的线程可以分为:守护线程和用户线程
一个守护线程就好像一个侍卫一样,守护着用户线程,只要还有一个用户线程在运行,那么守护线程就必须执行,只有当所有的用户线程都关闭了守护线程才能关闭。
守护线程的作用就是为其他线程提供便利服务,最经典的守护线程就是GC(垃圾回收器)。
多线程
多线程的概念
多线程:程序中包含多个执行单元,就是一个程序中可以同时运行多个不同的线程来执行不同的任务,就是说允许一个程序创建多个并行的线程来完成任务。例如:QQ可以有多个聊天窗口,一个窗口就是一个线程,QQ就是一个程序。
什么时候需要用多线程
程序需要执行多个任务时;
需要一些后台的程序时;
程序需要实现一些等待任务时。
多线程的缺点
- 如果线程过多,会影响性能
- 线程越多需要的存储空间越多
- 线程之间对共享资源的访问会相互影响,必须解决竞争共享资源的问题。
多线程的优点
- 提高程序的响应速度
- 提高CPU的利用率
- 改善程序结构,将复杂的任务分给多个线程,独立运行。
并行与并发
并行:一个时间节点,同时做多件事情。
并发:一个时间段内,依次做多件事。共享资源只能一个接着一个执行。
线程同步
线程同步:当一个线程发出某种功能调用时,在没有得到结果之前,其他线程为保证数据的一致性,不能再对该功能进行调用。
多个线程同时对同一份共享资源进行操作时,可能会引起数据混乱或者冲突。所以引入线程“同步”机制,即就是线程之间有先来后到的关系。
例如:取钱时,可以在手机上提现,也可以在TM机中提取,如果不添加同步机制是不是会出现,共有一千元,同时在手机和TM机上操作都能取出一千。
同步机制就是要求线程要排队,加锁。
为确保同一时间内只有一个线程访问共享资源。可以给共享资源加锁。
同步锁
同步锁可以是任何对象,但必须唯一,确保多线程获得是同一个对象(当做锁标记)。
同步执行过程:
- 第一个线程访问同步代码块,锁定同步对象,执行其中代码。
- 第二个线程访问同步代码块,发现同步对象被锁定,无法访问。
- 第一个线程执行完毕,释放锁对象。
- 第二个线程访问,同步对象没有锁,然后锁定锁对象并访问。
public class Ticket1 extends Thread {
static int num=10;
//static使锁对象为一
static Object obj=new Object();
@Override
public void run() {
while (true) {
//synchronized(同步对象) 同步对象可以是任意类的对象,但是只能是惟一的,独一份的.
//继承Thread类时,不能用this ,因为this表示当前对象不是独一份.
//对象中有一个区域叫对象头,对象头有一个记录锁状态区域
synchronized (obj){//进入同步代码块,加锁 此时将对象头中锁状态区域改为,加锁状态
try {
Thread.sleep(800);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (num>0){
System.out.println(Thread.currentThread().getName()+":"+num);
num--;
}else break;
}//执行完同步代码块,自动释放锁.
}
}
}
synchronized
synchronized可以修饰代码块,还可以修饰方法。
当修饰非静态方法时,它的锁标志的对象默认是this,不同对象this不相同。
当修饰静态放方法时,锁标志对象是类的class对象,只要是同一个类的对象,那么他们对应的class对象是相同的。
synchronized靠底层编译指令实现。
public class MyThread extends Thread {
static int count;
@Override
public void run() {
print();
}
public synchronized static void print(){
for (int i=0;i<5;i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+count++);
}
}
}
public class Test1 {
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
MyThread myThread1 = new MyThread();
myThread.setName("my1:");
myThread1.setName("my2->");
myThread.start();
myThread1.start();
}
}
/*结果:
my1:0
my1:1
my1:2
my1:3
my1:4
my2->5
my2->6
my2->7
my2->8
my2->9
*/
myThread和myThread1是MyThread的两个对象,因为run()方法中调用了静态方法print(),相当于myThread和myThread1用了同一把锁。
当一个类被加载到内存中的时候,会为每一个类创建一个class对象,来封装类的信息。如果只创建一个对象的两个线程,那么两个线程拿到的this是相同的。具体实例看多线程买票中的实现Runnable接口的方法。
多线程买火车票问题
购买火车票时,无论有多少种方式购买,但是票的总数是固定的。这就要求所有线程共享票数资源。用两种方式继承Thread类和实现Runnable接口实现不同线程购买票。
继承Thread类
public class Ticket1 extends Thread {
static int num=10;
//static使锁对象为一
static Object obj=new Object();
@Override
public void run() {
while (true) {
synchronized (obj){
try {
Thread.sleep(800);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (num>0){ System.out.println(Thread.currentThread().getName()+":"+num);
num--;
}else break;
}
}
}
}
public class Test {
public static void main(String[] args) {
Ticket1 ticket = new Ticket1();
ticket.setName("窗口1");
Ticket1 ticket1 = new Ticket1();
ticket1.setName("窗口2");
ticket.start();
ticket1.start();
}
}
实现Runnable接口
public class RunnableDemo implements Runnable {
int num = 10;
Object obj = new Object();
@Override
public void run() {
while (true){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (num>0) {
synchronized (obj){
System.out.println(Thread.currentThread().getName()+"买到票:"+num);
num--;
}
}else {
break;
}
}
}
}
public class Test {
public static void main(String[] args) {
RunnableDemo runnableDemo = new RunnableDemo();
Thread ticket1 = new Thread(runnableDemo);
ticket1.setName("窗口1");
Thread ticket2 = new Thread(runnableDemo);
ticket2.setName("窗口2");
ticket1.start();
ticket2.start();
}
}
因为在实现Runnable接口的方法中开启的两个线程都用的是同一个RunnableDemo对象所以两个线程的num和obj是相同的,无需加static修饰。
以上两个方法均使用到了synchronized锁,如果不适用则程序可以在任何地方被终止,可能会出现购买重票问题。
Lock(锁)
从jdk5.0开始,java提供了更加强大的线程同步机制——通过显示地定义同步锁对象来实现同步,锁对象使用Lock对象充当。
比较常用的是ReentrantLock,可以是显示地加锁和释放锁。
import java.util.concurrent.locks.ReentrantLock;
public class LockDemo extends Thread {
static int num=10;
static ReentrantLock lock = new ReentrantLock(false);
@Override
public void run() {
while (true) {
lock.lock();//加锁
try {
Thread.sleep(1000);
if (num>0){ System.out.println(Thread.currentThread().getName() + "的票是" + num + "号");
num--;
}
else {
break;
}
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();//释放锁
}
}
}
}
线程死锁
死锁:不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。
当出现死锁后不会抛出异常,也不会提示,只是所有线程都处于阻塞状态,无法继续。
举个例子:
中国人吃饭是用两只筷子,美国人是用刀和叉子,当中国人拿了一只筷子和一把刀,美国人拿了一只筷子和一把叉子,并且都不肯放手,此时就成了死锁状态。
//模拟死锁状态
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Dieth extends Thread {
static Object obj1 = new Object();
static Object obj2 = new Object();
boolean f ;
static Lock lock = new ReentrantLock();
public Dieth(boolean f) {
this.f = f;
}
@Override
public void run() {
if (f==true){
synchronized (obj1) {
lock.lock();
System.out.println("lock obj1");
synchronized (obj2) {//当请求锁标志obj2时,obj2已经被占用
System.out.println("lock obj2");
}
}
lock.unlock();
}
else {
synchronized (obj2){
lock.lock();
System.out.println("lock obj2");
synchronized (obj1){
System.out.println("lock obj1");
}
}
lock.unlock();
}
}
}
线程通信
线程通讯指的是多个线程通过相互牵制,相互调用,即线程间的相互作用。设计三个方法:
- .wait一旦执行此方法,当前线程就会进入阻塞状态,并释放同步监视器。
- .notify一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级最高的那个。
- .notifyAll一旦执行此方法,就会唤醒所有被wait的线程。
注:wait(),notify(),notifyAll()方法都必须使用在同步代码块中,或者是同步方法中。
两个线程交替打印数字
public class PringtNum extends Thread {
static int num = 0;
static Object obj = new Object();
@Override
public void run() {
while (true) {
synchronized (obj) {
if (num < 1000) {
obj.notify();//唤醒另一个线程
num++;
System.out.println(Thread.currentThread().getName() + ":" + num);
try {
if (num!=1000)
obj.wait();//此线程等待
} catch (InterruptedException e) {
e.printStackTrace();
}
} else break;
}
}
}
}
public class Test {
public static void main(String[] args) {
PringtNum pringtNum = new PringtNum();
PringtNum pringtNum1 = new PringtNum();
pringtNum.start();
pringtNum1.start();
}
}
经典例题:生产者/消费者问题
生产者(Productor)将产品放在柜台(Counter),而消费者(Customer)从柜台处取走产品,生产者一次只能生产固定数量的产品(比如:1), 这时柜台中不能再放产品,此时生产者应停止生产等待消费者拿走产品,此时生产者唤醒消费者来取走产品,消费者拿走产品后,唤醒生产者,消费者开始等待。
分析:应当有三个类(生产者,消费则,柜台),生产者生产商品可以使得柜台的货物属性值增加,而消费者可以使得柜台的货物属性值减少。每次只能有一方进行操作,则需要用到线程同信。
//柜台
public class Counter {
int num = 0;
public Counter(int num) {
this.num = num;
}
//生产商品
public synchronized void shengChan(){
if (num==0){
num++;
System.out.println("生产者制作商品:"+num);
this.notify();//唤醒消费者线程
}else {
try {
this.wait();//使自己等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//消费产品
public synchronized void xiaoFei(){
if (num>0){
num--;
System.out.println("消费者取走商品:"+num);
this.notify();//唤醒生产者线程
}else {
try {
this.wait();//使自己等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//生产者
public class Producter extends Thread {
Counter counter;
public Producter(Counter counter) {
this.counter = counter;
}
@Override
public void run() {
while (true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
counter.shengChan();
}
}
}
//消费者
public class Consumer extends Thread {
Counter counter;
public Consumer(Counter counter) {
this.counter = counter;
}
@Override
public void run() {
while (true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
counter.xiaoFei();
}
}
}
public class Test {
public static void main(String[] args) {
Counter counter = new Counter(1);//生产者和消费者使用的是同一个柜台对象
Producter p = new Producter(counter);
Consumer c = new Consumer(counter);
p.start();
c.start();
}
}