基本概念
程序:一段静态的代码;为了完成特定任务,用某种语言编写的一组指令的集合,
进程:正在运行的一个程序,是一个动态的过程(生命周期),进程作为资源分配的单位,系统在运行时会给每个进程分配不同的内存区域。
线程:一个程序内部的一条执行路径。线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器。
JDK中线程的几种状态
- 新建:当一个Thread类或者其子类的对象被声明并创建时,新生的线程对象处于新建状态
- 就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时具备了运行条件,只是还没分配到CPU资源。
- 运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run()方法定义了线程的操作和功能。
- 阻塞:在某种特殊的情况下,被人为的挂起或执行输入输出操作时,让出CPU并临时终止自己的运行,进入阻塞状态
- 死亡:线程完成全部工作或线程被提前强制性地终止或出现异常导致结束。
创建线程的方式一(继承Thread类)
- 创建一个类去继承Thread,
- 并重写run()方法。run()方法定义了线程的操作和功能,
- 创建子类对象,
- 调用此类对象的start()方法启动线程
//创建一个类去继承Thread
class MyThread extends Thread {
//2、重写Thread类的run()方法,
@Override
public void run() {
//打印偶数
for (int i = 0; i < 100; i++) {
if (i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
//Thread.currentThread().getName() 获取当前线程名
}
}
}
public class CreateThread01Method {
//main主线程
public static void main(String[] args) {
//3、创建Thread的类的子类对象
MyThread t1 = new MyThread();
//4、通过此类对象调用start()方法启动线程1
t1.start();
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
运行结果
创建线程的方式二(实现Runnable接口)
- 创建一个实现了Runnable接口的类
- 实现Runnbale中的抽象方法:run()
- 创建实现类的对象
- 将此对象作为参数传递到Thread类的构造器中,创建对象
- 通过创建的Thread类调用start()
//1.创建一个实现了Runnable接口的类
class RunnableThread implements Runnable {
//2.实现Runnbale中的抽象方法:run()
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
public class CreateThread02Method {
public static void main(String[] args) {
//3.创建实现类的对象
RunnableThread runnableThread = new RunnableThread();
//4.将此对象作为参数传递到Thread类的构造器中,创建对象
Thread t1 = new Thread(runnableThread);
Thread t2 = new Thread(runnableThread);
t1.setName("线程1");
t2.setName("线程2");
//5.通过创建的Thread类调用start()
t1.start();
t2.start();
}
}
解决多个线程对共享数据进行操作的线程安全问题
关键字:synchronized,或者 ReentrantLock类
方法一:同步代码块
synchronized(同步监视器) {
//对共享数据操作的代码块
}
/*说明:1.操作共享数据的代码,即为需要被同步的代码
* 2.共享数据,多个线程共同操作的变量,比如:ticket就是共享数据
* 3.同步监视器,俗称:锁,任何一个类的对象,都可以充当锁
* 要求:多个线程必须要公用一把锁
* /
对于继承类的方法创建的线程,锁可以是new一个静态的object类,也可以是继承类的子类的类(如上面的方式创建的可为:MyThread.class)
对于实现Runnable方法的可以直接用this关键字,以窗口卖票为例子,实现线程安全的卖票
- 继承Thread实现卖票
class Window extends Thread {
private static int ticket = 100;
private static Object object = new Object();
@Override
public void run() {
while (true) {
// synchronized (object) {
synchronized (Window.class) {
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (ticket > 0) {
System.out.println(getName() + ":卖出一张票,票号为:" + ticket--);
}else {
break;
}
}
}
}
}
public class SellTicket01 {
public static void main(String[] args) {
Window w1 = new Window();
Window w2 = new Window();
Window w3 = new Window();
w1.setName("1号窗口");
w2.setName("2号窗口");
w3.setName("3号窗口");
w1.start();
w2.start();
w3.start();
}
}
- 实现Runnable接口卖票
class Windows implements Runnable {
private int ticket = 100;
private Object object = new Object();
@Override
public void run() {
while (true) {
// synchronized (object) {
synchronized (this) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + ":卖出一张票,票号为:" + ticket--);
}else {
break;
}
}
}
}
}
public class SellTicket02 {
public static void main(String[] args) {
Windows w = new Windows();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("1号窗口");
t2.setName("2号窗口");
t3.setName("3号窗口");
t1.start();
t2.start();
t3.start();
}
}
方法二:同步方法,在方法加上synchronized关键字
- 继承Thread实现卖票二
class Window3 extends Thread {
private static int ticket = 100;
@Override
public void run() {
while (true) {
show();
}
}
private static synchronized void show() {//同步监视器为Window3.class
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + ":卖出一张票,票号为:" + ticket--);
}
}
}
public class SellTicket03 {
public static void main(String[] args) {
Window3 w1 = new Window3();
Window3 w2 = new Window3();
Window3 w3 = new Window3();
w1.setName("1号窗口");
w2.setName("2号窗口");
w3.setName("3号窗口");
w1.start();
w2.start();
w3.start();
}
}
- 实现Runnable接口实现卖票二
class Windows4 implements Runnable {
private int ticket = 100;
@Override
public void run() {
while (true) {
show();
}
}
private synchronized void show() {//同步监视器为this
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + ":卖出一张票,票号为:" + ticket--);
}
}
}
public class SellTicket04 {
public static void main(String[] args) {
Windows4 w = new Windows4();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("1号窗口");
t2.setName("2号窗口");
t3.setName("3号窗口");
t1.start();
t2.start();
t3.start();
}
}
方法三: 使用Lock显示锁(ReentrantLock)
/**
* 使用Lock显示锁,来解决同步问题===》jdk5新增
*
* synchronized与Lock的异同
* 相同:二者都可以解决线程安全问题
* 不同:synchronized机制再执行完相应代码以后,自动释放同步监视器
* Lock需要手动的启动同步(lock()),同时结束时也需要手动实现(unlock)
*
*
*/
class window05 implements Runnable {
private int ticekt = 100;
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true){
try {
lock.lock();
if (ticekt > 0) {
System.out.println(Thread.currentThread().getName() + ":卖出的票号为:" + ticekt--);
}else{
break;
}
}finally {
lock.unlock();
}
}
}
}
public class SellTicket05 {
public static void main(String[] args) {
window05 w = new window05();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("1号窗口");
t2.setName("2号窗口");
t3.setName("3号窗口");
t1.start();
t2.start();
t3.start();
}
}
创建线程的方式三(实现Callable接口)
- 实现Callable接口
/**
* 新增方式一:实现callable接口。 --- JDK5.0新增
*
* 1.call()可以有返回值
* 2.call()可以抛出异常,被外面的操作捕获,获取异常的信息
* 3.callable支持泛型
*
*/
//1.创建一个实现callable的实现类
class NumThread implements Callable {
//2.实现call方法,将此线程需要执行的操作声明在call()中
@Override
public Object call() throws Exception {
int sum = 0;
for (int i = 0; i <= 100; i++) {
sum += i;
}
return sum;
}
}
public class NewThreadMethod01 {
public static void main(String[] args) {
NumThread numThread = new NumThread();
FutureTask future = new FutureTask(numThread);
new Thread(future).start();
try {
Object value = future.get();
System.out.println("计算结果为:" + value);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
创建线程的方式四(线程池)
- 线程池
/**
* 线程池方式创建线程,常用
*
* 1.提供指定线程数量的线程池
* 2.执行指定线程的操作,需要提供实现runnable接口或callable接口实现类的对象
* 3.关闭连接池
*
*/
class Thread01 implements Callable {
@Override
public Object call() throws Exception {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
return null;
}
}
class Thread02 implements Callable {
@Override
public Object call() throws Exception {
for (int i = 0; i < 100; i++) {
if (i % 2 != 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
return null;
}
}
public class NewThreadMethod02 {
public static void main(String[] args) {
//1.提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
//设置线程池的属性
// System.out.println(service.getClass()); //获取类名ThreadPoolExecutor
// service1.setCorePoolSize(14);
// service1.setKeepAliveTime();
//2.执行指定线程的操作,需要提供实现runnable接口或callable接口实现类的对象
// service.execute(new Runnable()); //适合Runnable
service.submit(new Thread01());
service.submit(new Thread02());
//3.关闭连接池
service.shutdown();
}
}
- 线程通信案例代码:使用两个线程交替打印1到100
/**
* 线程通信:使用两个线程交替打印1到100的数
*
* wait():当前线程进入阻塞状态,并释放同步监视器
* notify():会唤醒欸wait的一个线程,如果有多个线程被wait,就唤醒优先级高的那一个
* notifyAll():唤醒所有被wait的线程
*
* 说明:
* 1.wait(),notify(),notifyAll()三个方法必须使用在同步代码块或者同步方法中
* 2.wait(),notify(),notifyAll()三个方法的调用者必须是同一个同步监视器,
* 否则会出现.IllegalMonitorStateException
*
* 面试题:sleep()和wait()的异同?
* 1.相同点:都可以使当前线程进入阻塞状态
* 2.不同点 1)两个方法声明的位置不同,sleep()声明在Thread中,wait()声明在Object中
* 2)调用的要求不同,sleep()可以在任何需要的场景下调用,wait()必须使用在同步代码块或者同步方法中
* 3)关于是否释放同步监视器,如果两个方法都使用在同步代码块或同步方法中,sleep不释放锁,wait释放锁
*
*
*/
class Number implements Runnable {
private int number = 1;
private Object object = new Object();
@Override
public void run() {
while (true) {
synchronized (this) {
// synchronized (object) {
// object.notify();
//唤醒线程
notify();
if (number <= 100) {
System.out.println(Thread.currentThread().getName() + ":" + number++);
try {
// object.wait();
//阻塞线程
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
break;
}
}
}
}
}
public class CommunicationDemo {
public static void main(String[] args) {
Number n = new Number();
Thread t1 = new Thread(n);
Thread t2 = new Thread(n);
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
}
}
- 关于生产者和消费者的案例代码
/**
* 生产者消费者,如果生产数量超过20个就先暂停生产,如果产品数量为0就不消费
*/
class Clerk {
private int productNumber = 0;
public synchronized void product() {
if (productNumber < 20) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "生产第" + (++productNumber) + "个产品");
notify();
}else {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void consumerProduct() {
if (productNumber > 0) {
System.out.println(Thread.currentThread().getName() + "消费第" + (productNumber--) + "个产品");
notify();
}else {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//生成者
class Producer extends Thread {
private Clerk clerk;
public Producer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
System.out.println(getName() + ":开始生成");
while (true) {
clerk.product();
}
}
}
//消费者
class Consumer extends Thread {
private Clerk clerk;
public Consumer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
System.out.println(getName() + ":开始消费");
while (true) {
clerk.consumerProduct();
}
}
}
public class ProducerConsumerDemo {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Producer producer = new Producer(clerk);
Consumer consumer = new Consumer(clerk);
Consumer consumer2 = new Consumer(clerk);
producer.setName("生产者1");
consumer.setName("消费者1");
consumer2.setName("消费者2");
producer.start();
consumer.start();
consumer2.start();
}
}
- 线程死锁案例代码
/**
* 演示线程死锁问题
*
* 1.死锁的理解:不同线程分别占用对方需要的同步资源不放弃
* 都在等待对方放弃自己需要的同步资源,就形成了线程死锁
*
* 2.说明
* 1)出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
* 2)使用同步时,要避免出现死锁
*/
public class DeadLock {
public static void main(String[] args) {
// StringBuilder s1 = new StringBuilder();
StringBuffer s1 = new StringBuffer();
StringBuffer s2 = new StringBuffer();
new Thread(){
@Override
public void run() {
synchronized (s1) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
s1.append("a");
s2.append("1");
synchronized (s2) {
s1.append("b");
s2.append("2");
}
System.out.println(s1);
System.out.println(s2);
}
}
}.start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (s2) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
s1.append("c");
s2.append("3");
synchronized (s1) {
s1.append("d");
s2.append("4");
}
System.out.println(s1);
System.out.println(s2);
}
}
}).start();
}
}