创建多线程
方式一
package com.pan;
class Window extends Thread{
private static int ticket = 100;
public void run(){
while(true){
if(ticket > 0){
System.out.println(this.getName() + ":卖票,票号为:" + ticket);
ticket--;
}
else{
break;
}
}
}
}
public class ThreadMethodTest {
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();
}
}
方式二
class Window 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 ThreadMethodTest {
public static void main(String[] args) {
Window w = new Window();
Thread thread = new Thread(w);
thread.start();
Thread t2 = new Thread(w);
t2.start();
}
}
Thread中的常用方法
- start():启动当前线程(调用当前线程的run())
- run()
- currentThread():静态方法,返回执行当前代码的线程
- getName():获取当前线程的名字
- setName():设置当前线程的名字,也可以通过构造方法的方式自定义线程的名字,例如:
class HelloThread extends Thread{
@Override
public void run(){
}
public HelloThread(String name){
super(name);
}
}
public class ThreadMethodTest {
public static void main(String[] args) {
HelloThread h1 = new HelloThread("T1");
System.out.println(h1.getName());//T1
}
}
- yield():释放当前cpu的执行权
- join():在线程A中调用b中的join(),此时A线程就进入阻塞状态,直到线程B执行结束之后,线程A才结束阻塞状态。
- sleep(Long millitime):使当前线程“睡眠”指定的millitime毫秒。在指定的millitime毫秒时间内,当前线程是阻塞状态
- isAlive():判断当前线程是否存活
线程的优先级
- MAX_PRIORITY:10
- MIN_PRIORITY:1
- NORM_PRIORITY:5
2.如何获取和设置当前线程的优先级 - getPriority():获取线程的优先级
- setPriority(int p):设置线程的优先级
说明:高优先级的线程占有cpu执行权的概率高于低优先级的线程。
线程的生命周期
线程安全
例子:创建三个窗口卖票,总票数为100张。
- 问题:买票过程中,出现了重票、错票–》出现了线程安全问题
- 问题出现的原因:当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也操作车票。
- 解决方案:当一个线程A在操作ticket时,其他线程不能参与进来,直到线程A操作完成ticket时,其他线程才可以开始操作ticket,这种情况即使线程A出现了阻塞,也不能改变。
- 在Java中,通过同步机制来解决线程安全问题。
- 方式一:同步代码块
synchronized(同步监视器){
//需要被同步的代码
}
说明:1.操作共享数据(多个线程共同操作的变量)的代码,即为需要被同步的代码
2.同步监视器俗称锁,任何一个类的对象都可以充当锁(多个线程必须共用一把锁)- 使用同步代码块解决实现Runnable接口创建多线程的方式的线程安全问题
注意:可以考虑使用this充当同步监视器
- 使用同步代码块解决实现Runnable接口创建多线程的方式的线程安全问题
class Window1 implements Runnable{
private int ticket = 100;
Object obj = new Object();
@Override
public void run() {
while(true) {
synchronized (obj) {
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
ticket--;
} else {
break;
}
}
}
}
}
public class WindowTest {
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();
}
}
- 使用同步代码块解决继承Thread类的方式的线程安全问题
注意:慎用this充当同步监视器,可以考虑使用当前类充当同步监视器。
class Window extends Thread{
private static int ticket = 100;
static Object obj = new Object();
public void run(){
while(true) {
synchronized (obj)/*synchronized (Window.class)*/ {
//此时不能用this,因为this代表t1,t2,t3三个对象
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(this.getName() + ":卖票,票号为:" + ticket);
ticket--;
} else {
break;
}
}
}
}
}
public class WindowTest2 {
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();
}
}
以上两个的输出:
窗口1:卖票,票号为:100
窗口1:卖票,票号为:99
窗口1:卖票,票号为:98
窗口1:卖票,票号为:97
窗口1:卖票,票号为:96
窗口1:卖票,票号为:95
窗口1:卖票,票号为:94
窗口1:卖票,票号为:93
窗口1:卖票,票号为:92
窗口1:卖票,票号为:91
窗口1:卖票,票号为:90
窗口1:卖票,票号为:89
窗口1:卖票,票号为:88
窗口1:卖票,票号为:87
窗口3:卖票,票号为:86
窗口3:卖票,票号为:85
窗口3:卖票,票号为:84
窗口3:卖票,票号为:83
窗口3:卖票,票号为:82
窗口3:卖票,票号为:81
窗口3:卖票,票号为:80
窗口3:卖票,票号为:79
窗口3:卖票,票号为:78
窗口3:卖票,票号为:77
窗口3:卖票,票号为:76
窗口3:卖票,票号为:75
窗口3:卖票,票号为:74
窗口2:卖票,票号为:73
窗口2:卖票,票号为:72
窗口2:卖票,票号为:71
窗口2:卖票,票号为:70
窗口2:卖票,票号为:69
窗口2:卖票,票号为:68
窗口2:卖票,票号为:67
窗口2:卖票,票号为:66
窗口2:卖票,票号为:65
窗口2:卖票,票号为:64
窗口2:卖票,票号为:63
窗口2:卖票,票号为:62
窗口2:卖票,票号为:61
窗口2:卖票,票号为:60
窗口2:卖票,票号为:59
窗口2:卖票,票号为:58
窗口2:卖票,票号为:57
窗口2:卖票,票号为:56
窗口2:卖票,票号为:55
窗口2:卖票,票号为:54
窗口2:卖票,票号为:53
窗口2:卖票,票号为:52
窗口2:卖票,票号为:51
窗口2:卖票,票号为:50
窗口2:卖票,票号为:49
窗口2:卖票,票号为:48
窗口2:卖票,票号为:47
窗口2:卖票,票号为:46
窗口2:卖票,票号为:45
窗口2:卖票,票号为:44
窗口2:卖票,票号为:43
窗口2:卖票,票号为:42
窗口2:卖票,票号为:41
窗口2:卖票,票号为:40
窗口2:卖票,票号为:39
窗口2:卖票,票号为:38
窗口2:卖票,票号为:37
窗口2:卖票,票号为:36
窗口2:卖票,票号为:35
窗口2:卖票,票号为:34
窗口2:卖票,票号为:33
窗口2:卖票,票号为:32
窗口2:卖票,票号为:31
窗口2:卖票,票号为:30
窗口2:卖票,票号为:29
窗口2:卖票,票号为:28
窗口2:卖票,票号为:27
窗口2:卖票,票号为:26
窗口2:卖票,票号为:25
窗口2:卖票,票号为:24
窗口2:卖票,票号为:23
窗口2:卖票,票号为:22
窗口2:卖票,票号为:21
窗口2:卖票,票号为:20
窗口2:卖票,票号为:19
窗口2:卖票,票号为:18
窗口2:卖票,票号为:17
窗口2:卖票,票号为:16
窗口2:卖票,票号为:15
窗口2:卖票,票号为:14
窗口2:卖票,票号为:13
窗口2:卖票,票号为:12
窗口2:卖票,票号为:11
窗口2:卖票,票号为:10
窗口2:卖票,票号为:9
窗口2:卖票,票号为:8
窗口2:卖票,票号为:7
窗口2:卖票,票号为:6
窗口2:卖票,票号为:5
窗口2:卖票,票号为:4
窗口2:卖票,票号为:3
窗口2:卖票,票号为:2
窗口2:卖票,票号为:1
Process finished with exit code 0
同步的方式解决了线程的安全问题,局限性是操作同步代码时,只能有一个线程参与,其他线程等待,相当于一个单线程的过程,效率低。
- 方式二:同步方法
如果操作共享数据的代码完整的声明在一个方法中,我们可以将此方法声明为同步的。 - 使用同步方法解决实现Runnable接口的方式的线程安全问题,如下:
class Window1 implements Runnable{
private int ticket = 100;
@Override
public void run() {
while(true) {
show();
}
}
public synchronized void show(){//默认同步监视器为:this
if (ticket > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
ticket--;
}
}
}
public class WindowTest {
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();
}
}
- 使用同步方法处理继承Thread类的线程安全问题,如下:
class Window extends Thread{
private static int ticket = 100;
static Object obj = new Object();
public void run(){
while(true) {
show();
}
}
public static synchronized void show(){//同步监视器:Window.class
/*public synchronized void show(){*///同步监视器:t1,t2,t3,这种解决方式是错误的
if (ticket > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
ticket--;
}
else{
return;
}
}
}
public class WindowTest2 {
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();
}
}
- 方式三:使用Lock锁方式
package com.hai;
import java.util.concurrent.locks.ReentrantLock;
/*
* 解决线程安全问题的方式之一:Lock锁
* */
class Window implements Runnable{
private int ticket = 100;
//1.实例化ReentrantLock
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while(true){
try {
//2.调用锁定方法lock()
lock.lock();
if(ticket > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":售票,票号为:" + ticket);
ticket--;
}else{
break;
}
} finally {
//3.调用解锁方法:unlock
lock.unlock();
}
}
}
}
public class LockTest {
public static void main(String[] args) {
Window w = new Window();
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与Lock的异同:
//相同:二者都可以解决线程安全问题
//不同:synchronized机制在执行完相应的同步代码以后,自动释放同步监视器,而Lock需要手动的启动同步(lock()),同时也需要手动结束同步(unlock())
关于同步方法:
- 同步方法仍然涉及到同步监视器,只是不需要我们显示的声明。
- 非静态的同步方法同步监视器是:this
- 静态的同步方法,同步监视器是:当前类本身
使用同步机制将单例模式中的懒汉式改写为线程安全
package com.pan;
/*
* 使用同步机制将单例模式中的懒汉式改写为线程安全的
*
* */
class Bank{
private Bank(){
}
private static Bank instance = null;
//方式一:
// public static synchronized Bank getInstance(){
// if(instance == null){
// instance = new Bank();
// return instance;
// }
// return instance;
// }
//方式二:
public static Bank getInstance() {
if (instance == null) {
synchronized (Bank.class){
if(instance == null){
instance = new Bank();
}
}
}
return instance;
}
}
public class BankTest {
}
死锁
one:
public class TheadTest {
public static void main(String[] args) {
StringBuffer s1 = new StringBuffer();
StringBuffer s2 = new StringBuffer();
new Thread(){
@Override
public void run(){
synchronized (s1){
s1.append("a");
s1.append("1");
try {
Thread.sleep(100);
} 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");
s1.append("3");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized (s1){
s1.append("d");
s2.append("4");
System.out.println(s1);
System.out.println(s2);
}
}
});
}
}
two:
class A{
public synchronized void foo(B b){//A类的对象:a
System.out.println("当前线程名:" + Thread.currentThread().getName() + "进入A实例的foo方法");
try{
Thread.sleep(200);
} catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("当前线程名:" + Thread.currentThread().getName() + "企图调用B实例的last方法");
b.last();
}
public synchronized void last(){
System.out.println("进入A类的last方法内部");
}
}
class B{
public synchronized void bar(A a){
System.out.println("当前线程名:" + Thread.currentThread().getName() + "进入了B实例的bar方法");
try{
Thread.sleep(200);
}catch(InterruptedException e){
e.printStackTrace();
}
a.last();
}
public synchronized void last(){
System.out.println("进入B类的last方法内部");
}
}
public class DeadLock implements Runnable {
A a = new A();
B b = new B();
public void init(){
Thread.currentThread().setName("主线程");
a.foo(b);
System.out.println("进入主线程之后");
}
public void run(){
Thread.currentThread().setName("副线程");
b.bar(a);
System.out.println("进入副线程之后");
}
public static void main(String[] args) {
DeadLock d1 = new DeadLock();
new Thread(d1).start();
}
}