多线程
程序、进程、线程基本概念
-
程序:指使用某种语言编写一组指令集合,即一段静态代码。
-
进程:程序的一次执行过程,或者正在运行的一个程序,包括:产生、存在和消亡的过程。
-
线程:程序内部的一条执行路径。
-
并行:多个CPU执行多个任务。
-
并发:一个CPU同时执行多个任务。
-
多线程的优点:提高程序的响应、提高CPU的利用率、改善程序的结构。
-
多线程使用情况:程序需要执行两个或者多个任务、程序需要实现等待任务如用户输入、用户登录、文件读写、网络操作等、需要后台运行的程序。
线程的创建
- 方式一:继承Thread类
public class MyThread extends Thread {
public MyThread() {
super();
}
@Override
public void run(){
for (int i = 0; i < 100; i++) {
System.out.println("子线程:"+i);
}
}
}
一个线程对象只能调用一次start()方法启动,如果重复调用了,则将抛出以上的异常“IllegalThreadStateException”。
- 方式二:实现Runnable接口:
public class MyThread1 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("子线程:"+i + Thread.currentThread());
}
}
}
@Test
public void test1(){
Runnable myThread1 = new MyThread1();
Thread thread = new Thread(myThread1);
thread.start();
for (int i = 0; i < 100; i++) {
System.out.println("主线程:"+ i + Thread.currentThread());
}
}
- 方式三:实现Callable方法
在JDK5.0之后新增加了Callable方法、以及可以使用线程池的方法。
1 与使用Runnable相比, Callable功能更强大些
相比run()方法,可以有返回值
方法可以抛出异常
支持泛型的返回值
需要借助FutureTask类,比如获取返回结果
public class CallableTest {
public static void main(String[] args) {
Callable t1 = new MyThread();
FutureTask<Integer> futureTask = new FutureTask<Integer>(t1);
new Thread(futureTask).start();
try {
Integer value = futureTask.get();
System.out.println(value);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
class MyThread implements Callable<Integer>{
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i < 100; i++) {
if(i %2 == 0){
System.out.println(i);
sum += i;
}
}
return sum;
}
}
- 方式四:使用线程池
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。
好处:- 提高响应速度(减少了创建新线程的时间)
- 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
- 便于线程管理
- corePoolSize:核心池的大小
- maximumPoolSize:最大线程数 keepAliveTime:线程没有任务时最多保持多长 时间后会终止
…
class MyThread2 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(i %2 == 0){
System.out.println(Thread.currentThread().getName() +":"+ i);
}
}
}
}
public class ThreadPool {
public static void main(String[] args) {
// 1.调用Executors的newFixedThreadPool(),返回指定线程数量的ExecutorService
ExecutorService executorService = Executors.newFixedThreadPool(10);
// 2.将Runnable实现类的对象作为形参传递给ExecutorService的submit()方法中,开启线程
executorService.execute(new MyThread2());
executorService.execute(new MyThread2());
executorService.execute(new MyThread2());
executorService.execute(new MyThread2());
// 3.结束线程的使用
executorService.shutdown();
}
}
- ** Thread类常用方法**
void start(): 启动线程,并执行对象的run()方法
run(): 线程在被调度时执行的操作
String getName(): 返回线程的名称
void setName(String name):设置该线程名称
static Thread currentThread(): 返回当前线程。在Thread子类中就
是this,通常用于主线程和Runnable实现类
static void yield():线程让步
暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程
若队列中没有同优先级的线程,忽略此方法
join() :当某个程序执行流中调用其他线程的 join() 方法时,调用线程将
被阻塞,直到 join() 方法加入的 join 线程执行完为止
低优先级的线程也可以获得执行
static void sleep(long millis):(指定时间:毫秒) 令当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,时间到后
重排队。
抛出InterruptedException异常
stop(): 强制线程生命期结束,不推荐使用
boolean isAlive():返回boolean,判断线程是否还活着
线程调度的策略
-
时间片调度:
-
抢占式:高优先级的线程抢占CPU
-
线程的优先级等级
MAX_PRIORITY:10
MIN _PRIORITY:1
NORM_PRIORITY:5 -
涉及的方法
getPriority() :返回线程优先值
setPriority(int newPriority) :改变线程的优先级 -
说明
线程创建时继承父线程的优先级
低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用
线程的生命周期
线程同步
多线程对共享数据操作时一个线程执行一半时另一个线程就参与进来,导致共享数据错误的问题。
- 方法一:使用Synchronized关键字,当一个使用此关键字时,一个线程获得了共享数据的访问权限,其他线程就处于等待状态,必须在此线程释放锁后才能通过优先级获取此共享数据的访问权限。
同步代码块
Synchronized(对象){
//需要同步的代码
}
同步方法
public Synchronized void foo (String s){
...
}
- 释放锁的操作:
- 当前线程的同步方法、同步代码块执行结束。
- 当前线程在同步代码块、同步方法中遇到break、return终止了该代码块、
该方法的继续执行。 - 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导
致异常结束。 - 当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线
程暂停,并释放锁。
- 线程的死锁:
- 不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃
自己需要的同步资源,就形成了线程的死锁
- 不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃
- 方式二:Lock(锁)
class A{
private final ReentrantLock lock = new ReenTrantLock();
public void m(){
lock.lock();
try{
//保证线程安全的代码; }
finally{
lock.unlock();
}
}
}
- Synchronized与lock对比:
- Lock是显示的锁,需要自己打开和关闭,Synchronized是隐式锁,出作用域就自动释 放。
- Lock只有代码块锁,synchronized有代码块锁和方法锁。
- 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有
更好的扩展性(提供更多的子类)。 - 建议使用顺序:Lock ->同步代码块(已经进入了方法体,分配了相应资源)->同步方 法(在方法体之外)
- 实例:
银行有一个账户。有两个储户分别向同一个账户存3000元,每次存1000,存3次。每次存完 打印账户余额。问题:该程序是否有安全问题,如果有,如何解决?
public class bank {
public static void main(String[] args) {
Account ac = new Account(0);
Runnable p1 = new Person(ac);
Runnable p2 = new Person(ac);
Thread thread1 = new Thread(p1);
Thread thread2 = new Thread(p2);
thread1.start();
thread2.start();
}
}
class Person implements Runnable{
private Account ac;
public Person(Account ac) {
this.ac = ac;
}
public Account getAc() {
return ac;
}
public void setAc(Account ac) {
this.ac = ac;
}
@Override
public void run() {
for (int i = 0; i < 3; i++) {
ac.setBalance(1000);
}
}
}
class Account{
int balance;
public Account(int balance) {
this.balance = balance;
}
public int getBalance() {
return balance;
}
public void setBalance(int balance) {
synchronized(Account.class){
System.out.println("账户"+Thread.currentThread()+"存入1000");
this.balance += balance;
print();
}
}
public void print(){
System.out.println("账户中存在:"+balance);
}
}
线程通信
-
wait() 与 notify() 和 notifyAll()
- wait():令当前线程挂起并放弃CPU、同步资源并等待,使别的线程可访问并修改共享资而 当前线程排队等候其他线程调用notify()或notifyAll()方法唤醒,唤醒后等待重新获得对监视 器的所有权后才能继续执行。
- notify():唤醒正在排队等待同步资源的线程中优先级最高者结束等待
- vnotifyAll ():唤醒正在排队等待资源的所有线程结束等待.
- 这三个方法只有在synchronized方法或synchronized代码块中才能使用,否则会报
java.lang.IllegalMonitorStateException异常。 5. 因为这三个方法必须有锁对象调用,而任意对象都可以作为synchronized的同步锁, 因此这三个方法只能在Object类中声明。
-
生产者消费者问题:生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处
取走产品,店员一次只能持有固定数量的产品(比如:20),如果生产者试图生产更多的产品,店员会叫生产者停一下,如果店中有空位放产品了再通知生产者继续生产;如果店中没有产品了,店员会告诉消费者等一下,如果店中有产品了再通知消费者来取走产品。
这里可能出现两个问题:
生产者比消费者快时,消费者会漏掉一些数据没有取到。
消费者比生产者快时,消费者会取相同的数据。
public class ProduceTest {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Runnable productor = new Productor(clerk);
Runnable customer = new Customer(clerk);
Thread t1 = new Thread(productor);
Thread t2 = new Thread(customer);
t1.start();
t2.start();
}
}
class Productor implements Runnable{
private Clerk clerk;
public Productor(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
System.out.println("生产者开始生产产品");
while (true){
try {
Thread.sleep((int)Math.random()*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.addProduct();
}
}
}
class Customer implements Runnable{
private Clerk clerk;
public Customer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
System.out.println("消费者开始消费产品");
while (true){
try {
Thread.sleep((int)Math.random()*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.getProduct();
}
}
}
class Clerk{
private int product = 0;
public synchronized void addProduct(){
if(product >= 20){
try {
wait();
}catch (InterruptedException e){
e.printStackTrace();
}
}else {
product++;
System.out.println("生产者生产第"+product+"个产品");
notifyAll();
}
}
public synchronized void getProduct(){
if(product <= 0){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
System.out.println("消费者取走第"+product+"个产品");
product--;
notifyAll();
}
}
}