一、线程的创建方式
第一种方式:继承Thread类的方式:
第二种方式:实现Runnable接口的方式:
第三种方式:实现Callable接口
//1.创建一个实现Callable的实现类
class NumThread implements Callable{
//2.实现call方法,将此线程需要执行的操作声明在call()中
@Override
public Object 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()的返回值。
Object sum = futureTask.get();
System.out.println("总和为:" + sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
第四种方法:使用线程池创建
此方法见其他文章单独说明
二、线程安全的解决:
方式一:同步代码块
1.操作共享数据的代码,即为需要被同步的代码。 -->不能包含代码多了,也不能包含代码少了。
2.共享数据:多个线程共同操作的变量。比如:ticket就是共享数据。
3.同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。
要求:多个线程必须要共用同一把锁。
①:当使用继承Thread类创建的线程时:方法中的变量应定义为静态变量,且做同步锁的类必须为静态类,因为此时当你创建多个线程是,每创建一个线程的同时会产生一个类,为了使所有线程均使用同一把锁,则需要设置为静态,类中的变量也是如此。
如图:三个窗口实现卖票
package com.cn.java1;
class Window3 extends Thread{
private static int ticket = 100;
private static Object obj = new Object();
@Override
public void run() {
while (true) {
synchronized (obj/Window3.class) {
//这里不能为this,this表示当前类的对象,而此时当前类的对象不同。 Window3.class表示类本身
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
ticket--;
} else {
break;
}
}
}
}
}
public class WindowTest3 {
public static void main(String[] args) {
Window3 t1 = new Window3();
Window3 t2 = new Window3();
Window3 t3 = new Window3();
t1.start();
t2.start();
t3.start();
t1.setName("窗口1:");
t2.setName("窗口2:");
t3.setName("窗口3:");
}
}
②Runnable接口来创建多线程,此时因为所有线程均使用同一个类,所以此时不需要使用静态变量和方法,如图:
class Window1 implements Runnable {
private int ticket = 100;
Object obj = new Object();
@Override
public void run() {
while (true) {
synchronized (obj) {
//此时的同步锁也可使用this,表示当前类的对象,因为全部均使用同一个类
if (ticket > 0) {
try {
Thread.sleep(1000);
} 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 s1 = new Thread(w);
Thread s2 = new Thread(w);
Thread s3 = new Thread(w);
s1.setName("窗口1");
s2.setName("窗口2");
s3.setName("窗口3");
s1.start();
s2.start();
s3.start();
}
}
方式二:同步方法
如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明同步的。
关于同步方法的总结:
①此时直接在方法下使用synchronized即可,同步监视器为this,即当前类的对象,三个窗口线程均使用w,所以相同
package com.cn.java1;
class Window2 implements Runnable {
private static int ticket = 100;
Object obj = new Object();
@Override
public void run() {
while (true) {
show();
}
}
private 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 WindowTest2 {
public static void main(String[] args) {
Window1 w = new Window1();
Thread s1 = new Thread(w);
Thread s2 = new Thread(w);
Thread s3 = new Thread(w);
s1.setName("窗口1");
s2.setName("窗口2");
s3.setName("窗口3");
s1.start();
s2.start();
s3.start();
}
}
②此时必须将同步的方法设置为静态方法,因为每个线程对应的类不同
package com.cn.java1;
class Window3 extends Thread{
private static int ticket = 100;
// private static Object obj = new Object();
@Override
public void run() {
while (true) {
show();
}
}
private static 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--;
}
}
}
public class WindowTest3 {
public static void main(String[] args) {
Window3 t1 = new Window3();
Window3 t2 = new Window3();
Window3 t3 = new Window3();
t1.start();
t2.start();
t3.start();
t1.setName("窗口1:");
t2.setName("窗口2:");
t3.setName("窗口3:");
}
}
方式三:Lock(锁) 注意当定义为true时,每个线程依次执行,当默认不填或为false时,线程和上述执行相同,谁抢到谁先执行
以下为Runnable接口实现线程,如果为继承时,ReentrantLock应定义为静态类。
package com.cn.java2;
import java.util.concurrent.locks.ReentrantLock;
class Window implements Runnable {
private int ticket = 100;
private ReentrantLock lock = new ReentrantLock(); //注意当定义为true时,每个线程依次执行,当默认不填或为false时,线程和上述执行相同,谁抢到谁先执行
@Override
public void run() {
while (true) {
try {
//调用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 {
//调用解锁方法
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())
四、建议会用顺序
Lock -> 同步代码块 -> 同步方法
就到这里啦,谢谢大家❥(^_-)