标题
多线程与并发
1.进程与线程
(1)什么是进程与线程?
进程:它是程序在处理机上的一次执行过程,是一个动态的概念。
进程是一个具有一定独立功能的程序,一个实体,每一个进程都有自己的地址空间。
进程的三种基本状态:Ready(就绪状态),Running(运行状态),Blocked(阻塞状态)。
线程:是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行,一个进程至少有一个线程。
(2)并发与并行的区别
并行:就是两个任务同时运行。
并发:就是两个任务同时请求运行,但是处理器一次只能接受一个任务,就会把两个任务安排轮流进行,由于CPU时间片运行的时间较短,就会感觉两个任务在同时运行。
2.线程实现的两种基本方式
(1) 第一种是通过继承Thread类
class Mythread extends Thread{
@Override
public void run() {
//逻辑处理
}
}
Mythread mt = new Mythread();
mt.start(); //启动线程
(2)第二种是通过实现Runnable接口
class Mythread implements Runnable{
@Override
public void run() {
//逻辑处理
}
}
Mythread mt = new Mythread();
Thread t = new Thread(mt);
t.start(); //启动线程
一般比较常用的是第二种方法,更加灵活一些。
3.join与中断线程
public final void join() throw InterruptedException
等待这个线程任务执行完毕。
InterruptedException —— 如果任何线程中断当前线程。当抛出此异常时,当前线程的中断状态将被清除。
(1)public void interrupt()
中断这个线程。
(2)自定义中断线程
通过定义个boolean类型的变量来控制线程的中断
话不多说,直接上代码
public class ThreadDemo {
public static void main(String[] args) {
MyRunable mr = new MyRunable();
Thread t= new Thread(mr);
t.start();//启动线程
for (int i = 0; i <50 ; i++) {
System.out.println(Thread.currentThread().getName() +"--"+i);
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (i == 20) {
mr.flag =false;//当i=20时,中断线程
}
}
}
}
class MyRunable implements Runnable{
public boolean flag = true;
public MyRunable(){
flag = true;
}
@Override
public void run() {
int i =0;
while(flag){
System.out.println(Thread.currentThread().getName()+"======="+(i++));
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
4.线程的同步
在多线程的操作中,多个线程有可能同时处理同一个资源。而解决数据共享问题,必须使用同步,同步就是指 多个线程在同一个时间段内只能有一个线程执行指定的代码,其他线程要等待当前线程完毕后才能继续执行。
线程同步有以下三种方法:
(1)同步代码块
synchronized(要同步的对象){
要进行的操作;
}
下面我用代码来模拟4个窗口来卖100张票的实例
public class ThreadDemo2 {
public static void main(String[] args) {
MyRunnable2 mr2 = new MyRunnable2();
Thread t1 = new Thread(mr2);
Thread t2 = new Thread(mr2);
Thread t3 = new Thread(mr2);
Thread t4 = new Thread(mr2);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class MyRunnable2 implements Runnable{
private int ticket = 100;//票数
@Override
public void run() {
for (int i = 0; i < 300; i++) {
if(ticket > 0){
synchronized (this) {
ticket--;
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("您购买的票剩余" + ticket + "张");
}
}
}
}
}
(2)通过同步方法
public synchronized void method(){
要进行的操作;
}
在上面的代码实例中,线程的共享资源是卖票,故只需要在MyRunnable2里写一个同步方法,还是直接附上代码。
//同步方法
private synchronized void method(){
if (ticket > 0){
ticket--;
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("您购买的票剩余" + ticket + "张");
}
}
写好之后,只需要在for循环里直接调用同步方法,即可实现线程同步。
(3)使用Lock (ReentrantLock)
Lock实现提供比使用Synchronized方法和语句可以获得的更广泛的锁定操作。它们允许更灵活的结构化,可能具有完全不同的属性,而且可以支持多个相关联的对象Conditon。
下面是我根据上面的同步方法进行修改的代码。
ReentrantLock lock = new ReentrantLock();//互斥锁
//通过Lock实现同步
private void method2(){
lock.lock();
try{
if (ticket > 0){
ticket--;
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("您购买的票剩余" + ticket + "张");
}
}finally{
lock.unlock();//释放锁
} //加上try-finally语句保证锁一定会释放,避免死锁问题。
}
5.死锁
过多的同步有可能出现死锁,死锁的操作一般是在程序运行的时候才有可能出现。还是直接上代码。
public class DeadThreadDemo{
public static void main(String[] args){
new DeadThread();
}
}
//顾客
class Customer{
public synchronized void say(Waiter w){
System.out.println("顾客说:先吃饭再买单!");
w.doService();
}
public synchronized void doService(){
System.out.println("同意了,买完单再吃饭!");
}
}
//服务员
class Waiter{
public synchronized void say(Customer c){
System.out.println("服务员说:先买单再吃饭!");
w.doService();
}
public synchronized void doService(){
System.out.println("同意了,吃完饭再买单!");
}
}
//死锁
class DeadThread implements Runnable{
Customer c = new Customer();
Waiter w = new Waiter();
public DeadThread(){
new Thread(this).start();
w.say(c);
}
public void run(){
c.say(w);
}
}
运行之后即会发现,就会程序就会停住。原因是在同步方法中调用了另一个对象的同步方法,可能产生死锁。
6.线程池
线程池是预先创建线程的一种技术。线程池在还没有任务到来之前,创建一定数量的线程,放入空闲队列中,然后对这些资源进行复用。减少频繁的创建和销毁对象。
java里面的线程池接口是Executor,是一个执行线程的工具。
线程池接口是ExecutorService。