1. 线程的生命周期
public enum State {
/**
* Thread state for a thread which has not yet started.
*/
NEW,
/**
* Thread state for a runnable thread. A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.
*/
RUNNABLE,
/**
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@link Object#wait() Object.wait}.
*/
BLOCKED,
/**
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
* <ul>
* <li>{@link Object#wait() Object.wait} with no timeout</li>
* <li>{@link #join() Thread.join} with no timeout</li>
* <li>{@link LockSupport#park() LockSupport.park}</li>
* </ul>
*
* <p>A thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* For example, a thread that has called <tt>Object.wait()</tt>
* on an object is waiting for another thread to call
* <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
* that object. A thread that has called <tt>Thread.join()</tt>
* is waiting for a specified thread to terminate.
*/
WAITING,
/**
* Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
* <ul>
* <li>{@link #sleep Thread.sleep}</li>
* <li>{@link Object#wait(long) Object.wait} with timeout</li>
* <li>{@link #join(long) Thread.join} with timeout</li>
* <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
* <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
* </ul>
*/
TIMED_WAITING,
/**
* Thread state for a terminated thread.
* The thread has completed execution.
*/
TERMINATED;
}
2. 线程的同步机制
- 知识概述
/**
* 例题:开启三个窗口售票,总票数为100张。
* <p>
* 使用继承Thread类的方式实现
*
* 1. 程序执行中出现了重票、错票
* 2. 如何解决?需要一个线程针对于ticket完全操作结束之后,其他的线程才能进入继续操作ticket
*
* 3. 代码层面如何解决?线程的同步机制。
*
* 4. 同步机制的实现方式:
* 方式一:同步代码块
* synchronized(同步监视器){
* //需要被同步的代码
* }
* 说明:① 需要被同步的代码,即为操作共享数据的代码
* ② 共享数据? 多个线程共同操作的数据
* ③ 同步监视器,即为一个对象。俗称:锁
* 要求:① 任何一个类的对象都可以充当同步监视器。
* ② 多个线程必须共享同一个同步监视器。
*
*
* 方式二:同步方法
* 如果操作共享数据的代码完整的声明在一个或多个方法中,此时可以考虑将此方法声明为同步方法。
*
* 对于非静态的同步方法而言,默认的同步监视器是:this
* 对于静态的同步方法而言,默认的同步监视器是:当前类.class
*
*
* 测试使用同步代码块解决继承Thread类的方式中的线程安全问题。
*
* 小结:针对同步监视器:实现Runnable的方式中可以考虑使用this。在继承Thread类的方式时,慎重this。
* 可以考虑使用的同步监视器是:当前类.class
* 针对于操作共享数据的代码:不能“包”少了,也不能“包”多了
*
* 5. 同步机制:好处:解决了线程的安全问题。
* 缺点:执行同步代码块的过程中,其实是单线程的。效率低。
*/
2.1 同步代码块解决继承Thread中的线程安全问题
class Window extends Thread {
static int ticket = 100;//初始票数100张
static Object obj = new Object();
@Override
public void run() {
while (true) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
// synchronized(obj){
synchronized(Window.class){ //Class clazz = Window.class
//不能使用this
// synchronized(this){ //此时的this代表w1,w2,w3
if (ticket > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖票,当前票号为:" + ticket);
ticket--;
} else {
break;
}
}
}
}
}
public class WindowTest {
public static void main(String[] args) {
Window t1 = new Window();
Window t2 = new Window();
Window t3 = new Window();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
2.2 同步代码块解决实现Runnable中的线程安全问题
class Window1 implements Runnable{
int ticket = 100;
// Object obj = new Object();
// Dog dog = new Dog();
@Override
public void run() {
while(true){
synchronized (this){ //此时的this表示w,是唯一的
// synchronized (dog){
if(ticket > 0){
try {
Thread.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖票,当前票号为:" + ticket);
ticket--;
}else{
break;
}
}
}
}
}
public class WindowTest1 {
public static void main(String[] args) {
Window1 w = new Window1();
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();
}
}
class Dog{
}
2.3 同步方法解决继承Thread中的线程安全问题
class Window2 implements Runnable {
int ticket = 100;
@Override
public void run() {
while (true) {
// synchronized (this){
show();
// }
}
}
public synchronized void show() { //默认的同步监视器是this,此题中代表:w
if (ticket > 0) {
try {
Thread.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖票,当前票号为:" + ticket);
ticket--;
}
}
}
public class WindowTest2 {
public static void main(String[] args) {
Window2 w = new Window2();
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();
}
}
2.4 同步方法解决实现Runnable中的线程安全问题
class Window3 extends Thread {
static int ticket = 100;//初始票数100张
@Override
public void run() {
while (true) {
show();
}
}
public static synchronized void show(){ //此时的同步监视器是:this。此题中表示:t1,t2,t3
if (ticket > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖票,当前票号为:" + ticket);
ticket--;
}
}
}
public class WindowTest3 {
public static void main(String[] args) {
Window3 t1 = new Window3();
Window3 t2 = new Window3();
Window3 t3 = new Window3();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
- 练习
3. 单例模式:懒汉式的线程安全的实现
public class SingletonTest1 {
public static void main(String[] args) {
Runtime r1 = Runtime.getInstance();
Runtime r2 = Runtime.getInstance();
System.out.println(r1 == r2);
}
}
//懒汉式
class Runtime {
//1. 私有化类的构造器
private Runtime() {
}
//2. 声明当前类的一个变量
private static Runtime instance = null;
//3. 调用方法,返回当前类的一个实例
//方式1:
// public static synchronized Runtime getInstance(){
//
// if(instance == null){
//
// instance = new Runtime();
// }
// return instance;
// }
//方式2:比方式1好
// public static Runtime getInstance() {
//
// synchronized (Runtime.class) {
// if (instance == null) {
//
// instance = new Runtime();
// }
// }
// return instance;
// }
//方式3:推荐
public static Runtime getInstance() {
if(instance == null){
synchronized (Runtime.class) {
if (instance == null) {
instance = new Runtime();
}
}
}
return instance;
}
}
4. 死锁
/**
* 死锁:不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
*
* 出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
*
*/
public class DeadLockTest {
public static void main(String[] args) {
StringBuilder s1 = new StringBuilder();
StringBuilder s2 = new StringBuilder();
new Thread(){
@Override
public void run() {
synchronized (s1){
s1.append("a");
s2.append("1");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
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){
s1.append("c");
s2.append("3");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s1){
s1.append("d");
s2.append("4");
System.out.println(s1);
System.out.println(s2);
}
}
}
}).start();
}
}
5. 线程的通信
/**
* 例题:使用两个线程打印 1-100。线程1, 线程2 交替打印
*
* 线程的通信:
*
* wait():一旦执行此方法,就会使得执行此方法所在的线程进行阻塞状态。同时释放同步监视器。
* notify():一旦执行此方法,就会唤醒被wait()的线程中优先级最高的那个线程。在获得同步监视器的情况下,
* 继续执行被wait()之后的代码。
* notifyAll():一旦执行此方法,就会唤醒所有被wait()的线程。在获得同步监视器的情况下,继续执行被wait()之后的代码。
*
*
* 说明:1. wait()/ notify() / notifyAll() :必须使用在同步代码块或同步方法中。
* 2. wait()/ notify() / notifyAll()的调用者必须是同步代码块或同步方法中的同步监视器。
* 否则,会报:IllegalMonitorStateException
* 3. wait()/ notify() / notifyAll() 是定义在java.lang.Object中。
*
*
* 高频面试题:sleep() 和 wait()的异同?
* 1. 两个方法声明的位置?Thread:sleep() / Object:wait()
* 2. 使用的范围有没有要求? sleep():在任何想使用的地方。 wait():必须使用在同步代码块或同步方法中
* 3. 如果都放在同步代码块或同步方法中,是否释放锁? sleep():不会 wait():会
* 4. 都会导致线程进入阻塞状态。但是结束阻塞状态的方式不同? sleep():时间到就结束阻塞。 wait():需要被唤醒
*
*/
class PrintNumber implements Runnable{
int number = 1;
Object obj = new Object();
@Override
public void run() {
while(true){
synchronized (obj) {
//唤醒被wait()的线程
obj.notify();
if(number <= 100){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + number);
number++;
}else{
break;
}
//当前的线程进入阻塞
try {
obj.wait(); //会释放同步监视器
} catch (InterruptedException e) {
e.printStackTrace();
}
//。。。。
}
}
}
}
public class CommunicationTest {
public static void main(String[] args) {
PrintNumber num = new PrintNumber();
Thread t1 = new Thread(num);
Thread t2 = new Thread(num);
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
}
}
面试题:sleep() 和 wait()的异同
- 生产者消费者题目
/**
* 生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品,
* 店员一次只能持有固定数量的产品(比如:20),如果生产者试图生产更多的产品,
* 店员会叫生产者停一下,如果店中有空位放产品了再通知生产者继续生产;如果店中没有产品了,
* 店员会告诉消费者等一下,如果店中有产品了再通知消费者来取走产品。
*
*
* 分析:
* 1. 是不是多线程问题?是 生产者线程、消费者线程
* 2. 会出现线程安全问题吗?会。有共享数据
* 3. 共享数据是什么? 产品的数量
* 4. 是否存在线程的通信? 有!
*
*/
public class ProducterConsumerTest {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Producter p1 = new Producter(clerk);
Consumer c1 = new Consumer(clerk);
Consumer c2 = new Consumer(clerk);
p1.setName("生产者1");
c1.setName("消费者1");
c2.setName("消费者2");
p1.start();
c1.start();
c2.start();
}
}
class Clerk{//店员
int productNum;//产品数据
//添加产品
public synchronized void addProduct(){
if(productNum < 20){
productNum++;
System.out.println(Thread.currentThread().getName() + "生产了第" + productNum + "个产品");
notifyAll();
}else{
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//减少产品
public synchronized void consumeProduct(){
if(productNum > 0){
System.out.println(Thread.currentThread().getName() + "消费了第" + productNum + "个产品");
productNum--;
notifyAll();
}else{
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Producter extends Thread{ //生产者
Clerk clerk;
public Producter(Clerk clerk){
this.clerk = clerk;
}
@Override
public void run() { //生产产品
System.out.println(Thread.currentThread().getName() + "开始生产产品");
while(true){
try {
Thread.sleep(25);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.addProduct();
}
}
}
class Consumer extends Thread{ //消费者
Clerk clerk;
public Consumer(Clerk clerk){
this.clerk = clerk;
}
@Override
public void run() { //消费产品
System.out.println(Thread.currentThread().getName() + "开始消费产品");
while(true){
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.consumeProduct();
}
}
}
6. 了解:线程的另外的2种创建方式
6.1 实现Callable接口
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/*创建多线程的方式三:实现Callable (jdk5.0新增的)
*/
//1.创建一个实现Callable的实现类
class NumThread implements Callable<Integer>{
//2.实现call方法,将此线程需要执行的操作声明在call()中
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i <= 100; i++) {
if(i % 2 == 0){
System.out.println(i);
sum += i;
}
}
return sum;
}
}
public class ThreadNew {
public static void main(String[] args) {
//3.创建Callable接口实现类的对象
NumThread numThread = new NumThread();
//4.将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
FutureTask futureTask = new FutureTask(numThread);
//5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
new Thread(futureTask).start();
// 接收返回值
try {
//6.获取Callable中call方法的返回值
//get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值。
Integer sum = (Integer) futureTask.get();
System.out.println("总和为:" + sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
面试题:使用Runnable和Callable的对比?
1.结论:实现Callable的方式好
2.体现:
① 可以在call方法中抛出异常,更灵活
② call方法,相较于run()可以有返回值
③ 可以通过泛型的方法,指定返回值的确定类型。
6.2 使用线程池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
//创建并使用多线程的第四种方法:使用线程池
//使用线程池创建多线程的好处:
//1.降低了资源的消耗,使用完的线程可以被复用。
//2.提高了程序的响应速度。
//3.便于管理。
class NumberThread implements Runnable{
@Override
public void run() {
for(int i = 0;i <= 100;i++){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
}
class NumberThread1 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. 提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
// //设置线程池的属性
// System.out.println(service.getClass());//ThreadPoolExecutor
service1.setMaximumPoolSize(20);
//2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
service.execute(new NumberThread());//适合适用于Runnable
service.execute(new NumberThread1());//适合适用于Runnable
// service.submit(Callable callable);//适合使用于Callable
//3.关闭连接池
service.shutdown();
}
}
7. 了解:Lock的方式解决线程安全问题
import java.util.concurrent.locks.ReentrantLock;
class Window implements Runnable{
int ticket = 100;
private final ReentrantLock lock = new ReentrantLock();
public void run(){
while(true){
try{
lock.lock();
if(ticket > 0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(ticket--);
}else{
break;
}
}finally{
lock.unlock();
}
}
}
}
public class ThreadLock {
public static void main(String[] args) {
Window t = new Window();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t1.start();
t2.start();
}
}
8. 集合框架的概述
- 了解
数组在内存存储方面的特点:
数组初始化以后,长度就确定了。
数组声明的类型,就决定了进行元素初始化时的类型。
数组在存储数据方面的弊端:
数组初始化以后,长度就不可变了,不便于扩展
数组中提供的属性和方法少,不便于进行添加、删除、插入等操作,且效率不高。同时无法直接获取存储元素的个数
数组存储的数据是有序的、可以重复的。---->存储数据的特点单一
/* 二、集合框架
* java.util.Collection:存储一个一个的数据
* |----java.util.List:存储有序的、可重复的数据。 ----> “动态”数组
* |---- ArrayList\LinkedList\Vector
* |----java.util.Set:存储无序的、不可重复的数据 ----> 高中学的"集合"
* |---- HashSet \ LinkedHashSet \TreeSet
*
* java.util.Map:存储一对一对的数据 (key-value) ----> 高中学的"函数" y = f(x) y = 2 * x + 1 (x1,y1),(x2,y2)
* |---- HashMap\LinkedHashMap\TreeMap\Hashtable\Properties
*
* 三、三个层面(递进关系)
* 层次一:掌握不同接口的主要实现类的实例化,及常用方法 (ArrayList\HashSet\HashMap)
* 层次二:掌握不同实现类之间的区别
* 层次三:常用的集合类的底层实现原理--->数据结构。 (ArrayList\LinkedList\HashMap)
*/