一、并发,并行、进程、线程
并发和并行
并发:我正在吃饭,来电话了,我可以接完电话以后回来接着吃饭。
并行:我可以一边吃饭,一边接电话。
所以关键区别在于:是否同时。
对于单核系统,只能做到并发;对于多核系统,可以做到并行,多个CPU同时处理多件事情。
进程和线程
一个进程内有多个线程
线程调度:
- 分时调度:轮流使用CPU,平均分配
- 抢占式调度:优先让优先级高的线程使用CPU,如果优先级相同,则随机分配给某个线程
二、创建线程
方法1:继承Thread类
- 需要继承Thread类
- 需要重写run方法
- 调用时不调用run方法,而是调用start方法
```java
class MyThread extends Thread {
@Override
public void run() {
} }for (int i = 0; i < 10; i++) {
System.out.println("新开的线程:" + i);
}
public class ThreadTest { public static void main(String[] args) { MyThread myThread = new MyThread(); myThread.start(); } }
<a name="mrdNJ"></a>
## 方法2:实现Runnable接口
1. 类需要实现Runnable接口;
1. 重写run方法;
1. 调用时,先new该类,然后将该类作为new Thread的参数传入;
1. 该Thread对象调用start方法。
```java
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("新线程");
}
}
public class ThreadTest1 {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread myThread = new Thread(myRunnable);
myThread.start();
}
}
实现Runnable接口通常比继承Thread类更好。
三、常用方法
// 主动休眠
public static void sleep(long millis)
// 主动放弃时间片,回到就绪状态
public static void yield()
// 允许其他线程加入到当前线程
public final void join()
// 设置优先级,1-10,默认为5
public void setPriority(int)
// 设置为守护进程
public void setDaemon(boolean)
优先级
优先级越高,获得较多的运行机会。只能反映线程的紧急程度,不能决定是否一定先执行。
休眠
Thread.sleep(1000);
是静态方法,在哪个线程里调用就让哪个线程休眠,例如,在main方法里调用Thread.sleep(1000); 就会让main线程睡眠。
让步
Thread.yield()
静态方法,让步给其他线程,但并不能保证其他线程先执行完再执行该线程。
sleep和yield的区别
sleep的线程,指定时间内一定不会被执行。
yield的线程,只是进入到可执行状态,有可能马上又被执行。
线程的合并
即在线程A,线程B调用join方法, 那么线程A需要等待线程B完全执行结束以后,才能接着执行线程A。
通常需要在A需要用到B中的结果才使用join方法。
package com.tuling.part1;
class JoinThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("Join线程:" + i);
}
}
}
public class TestJoin {
public static void main(String[] args) {
System.out.println("主线程开始");
Thread joinThread = new JoinThread();
joinThread.start();
try {
// joinThread执行结束以后,主线程才能接着执行。
joinThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线程结束");
}
}
四、守护线程
守护线程就是,如果在线程A里有守护线程B,A线程如果结束了,即便线程B没有结束,也将随着A线程结束,这就是“守护”
方式:
deamonThread.setDeamon();
class DeamonThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("守护线程: " + i);
}
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class TestDeamon {
public static void main(String[] args) {
Thread deamonThread = new DeamonThread();
deamonThread.setDaemon(true);
deamonThread.start();
for (int i = 0; i < 20; i++) {
System.out.println("主线程:" + i);
}
System.out.println("主线程结束啦");
}
}
以上代码,守护线程并不会输出100次,因为主线程结束后,守护线程也随着结束了。
五、线程的生命周期
六、线程安全
为什么会出现线程安全问题?
当多线程时,CPU在多个线程间高速切换,当涉及写操作时,可能出现写还未完成,又被另一个线程读取的情况,导致数据的不统一。
解决方案?
- 同步代码块;
- 同步方法;
- 锁。
同步代码块
即用关键字synchronized来将有可能出现线程安全问题的代码包裹起来,每次只允许一个线程进来执行其中的代码。
package com.tuling.part1;
class Ticket implements Runnable {
private int tickets = 100;
private Object lock = new Object();
@Override
public void run() {
while (true) {
synchronized (lock) {
if (tickets > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在卖第" + tickets-- + "张票");
}
}
}
}
}
public class TestSychronized {
public static void main(String[] args) {
Ticket ticket = new Ticket();
Thread t1 = new Thread(ticket, "窗口1");
Thread t2 = new Thread(ticket, "窗口2");
Thread t3 = new Thread(ticket, "窗口3");
t1.start();
t2.start();
t3.start();
}
}
可以用任意类型的对象来作为锁,例如上述代码可以用this作为锁,因为这个runnable的对象只有唯一的一个。
但是,多个线程,必须使用同一把锁!!!
同步方法
即用sychronized修饰一个方法,使其成为一个同步方法。
不需要手动上锁,如果是非静态方法会默认使用this作为锁,如果是静态方法,默认使用类名.class作为锁。
package com.tuling.part1;
class Ticket1 implements Runnable {
private int tickets = 10000;
@Override
public void run() {
while (true) {
sellTicket();
}
}
public synchronized void sellTicket() {
if (tickets > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在卖第" + tickets-- + "张票");
}
}
}
public class TestSychronizedMethod {
public static void main(String[] args) {
Ticket1 ticket = new Ticket1();
Thread t1 = new Thread(ticket, "窗口1");
Thread t2 = new Thread(ticket, "窗口2");
Thread t3 = new Thread(ticket, "窗口3");
t3.setPriority(1);
t1.start();
t2.start();
t3.start();
}
}
锁
- JDK5加入,更加灵活
- 提供更多实用方法,功能更强大,性能更优越
方式:
- 先new一个锁对象;
- lock.lock();锁住
- 结束后,lock.unlock();释放
ps: 一定要用try finally包裹住,在finally里中释放锁,否则如果发生异常,锁永远释放不了导致死锁。
class Ticket2 implements Runnable {
private int tickets = 1000;
Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
lock.lock();
try {
if (tickets > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在卖第" + tickets-- + "张票");
}
} finally {
lock.unlock();
}
}
}
}
public class TestLock {
public static void main(String[] args) {
Ticket2 ticket = new Ticket2();
Thread t1 = new Thread(ticket, "窗口1");
Thread t2 = new Thread(ticket, "窗口2");
Thread t3 = new Thread(ticket, "窗口3");
t1.start();
t2.start();
t3.start();
}
}
七、线程通信
有时候,我们需要多个线程按照顺序去执行,就需要用到线程通信。我们用等待唤醒机制来解决。
- 用sychronized来构建同步代码块,并加锁 (任意object即可)。
- lock.wait()进入等待;
- 在其他线程里通过lock.notify()来唤醒等待的线程。
- 或者通过lock.notifyAll()来唤醒所有等待的线程。
八、死锁
两个线程,各自在等对方释放锁,就会发生死锁。对于多个线程也是一样。
如下代码:
A拿到了lock1, B拿到了lock2, 接下来B先醒来(因为只睡了1s),想要去拿lock1,但是拿不到,因为被A占着。
然后A醒来,要去拿lock2, 但是拿不到,因为被B占着。于是两个线程各自等待对方释放锁,导致发生死锁。
public class TestDeadLock {
public static Object lock1 = new Object();
public static Object lock2 = new Object();
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock1) {
System.out.println("A完成了lock1");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock2) {
System.out.println("A完成了lock2");
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock2) {
System.out.println("B完成了lock2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock1) {
System.out.println("B完成了lock1");
}
}
}
}).start();
}
}
九、线程池
因为频繁的生成和销毁线程效率不高,因此使用线程池来解决这个问题。
线程池的使用步骤:
创建线程池对象:
// 使用Executors,创建包含3个线程的线程池。 ExecutorService pool = Executors.newFixedThreadPool(3);
创建Runnable接口子类对象;
MyRunnable myRunnable = new MyRunnable();
提交Runnable接口子类对象:
pool.submit(myRunnable);
完整代码如下: ```java package com.tuling.part1;
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors;
class MyRunnable1 implements Runnable { @Override public void run() { System.out.println(“——我需要一个游泳教练——“); System.out.println(“——游泳——-“); System.out.println(“——游泳结束,回去了——“); } }
public class TestThreadPool { public static void main(String[] args) { // 生成线程池对象 ExecutorService pool = Executors.newFixedThreadPool(3);
// 生成Runnable对象
MyRunnable1 myRunnable1 = new MyRunnable1();
// 提交Runnable对象
pool.submit(myRunnable1);
pool.submit(myRunnable1);
pool.submit(myRunnable1);
pool.submit(myRunnable1);
pool.submit(myRunnable1);
}
}
ps: 虽然线程池中只有3个可用线程,但是可以提交数 > 3, 因为会先提交3个,执行完毕后,线程回到线程池,发现还有任务未完成,就会接着去执行任务。这样就避免了反复创建线程的过程。
<a name="yYdJi"></a>
## 创建线程池对象的多种方式:
1. newFixedThreadPool(int); 创建固定长度的线程池
1. newCachedThreadPool(); 创建可伸缩的线程池,不用指定数量。
1. newSingleThreadPoolExecutor(); 创建单线程的Executor
1. newScheduledThreadPool(int), 固定长度的线程池,而且以延迟或者定时来执行,类似Timer;
<a name="lpgig"></a>
## Callable接口
相比于Runnable接口,Callable接口可以有返回值。但是需要依靠线程池,返回值是一个Future对象。
1. 生成线程池对象;
1. 生成Callable对象,需要实现call方法;
1. 提交Callable对象,得到Future类型的返回值;
1. 通过future.get()方法获取返回值。
```java
package com.tuling.part1;
import java.util.concurrent.*;
class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
return (int)(Math.random() * 10);
}
}
public class TestCallable {
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(3);
MyCallable myCallable = new MyCallable();
Future<Integer> future = pool.submit(myCallable);
Future<Integer> future1 = pool.submit(myCallable);
Future<Integer> future2 = pool.submit(myCallable);
try {
System.out.println(future.get());
System.out.println(future1.get());
System.out.println(future2.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
pool.shutdown();
}
}
十、线程安全集合
CopyOnWriteArray
该集合是线程安全的,用来替换ArrayList。
如果在多线程的情况下对ArrayList进行写入,将会出现线程安全问题,例如写入被覆盖或者不成功。因此需要使用CopyOnWriteArray来解决。它在写入的时候加了锁,防止出现线程安全问题。
CopyOnWriteArraySet
该集合用来替换HashSet
CocurrentHashMap
用来替换HashMap,不是对整个map加锁,而是对每个segment加锁。
默认情况下有16个segment, 就是对底层数组中的每个元素加锁。