1 创建线程
创建线程的方式总共有四种:
第一种是继承Thread类方式
第二种是实现Runnable接口方式
第三种是实现Callable接口
第四种是线程池:提供了一个线程队列,队列中保存着所有等待状态的线程。避免了创建与销毁额外开销,提高了响应的速度。
第一种:继承Thread类,重写run() 方法,调用start开启线程
注意,线程开启不一定立即执行,由cpu调度执行
public class TestThread1 extends Thread {
@Override
public void run() {
//run 方法线程体
for(int i=0;i<20;i++){
System.out.println("我在看代码————"+i);
}
}
public static void main(String[] args) {
//mian 线程,主线程
//创建一个线程对象
TestThread1 testThread1 =new TestThread1();
//调用start()方法开启线程
testThread1.start();
for (int i = 0; i < 20; i++) {
System.out.println("我在学习线程--"+i);
}
}
}
输出:
我在学习线程--0
我在学习线程--1
我在学习线程--2
我在学习线程--3
我在学习线程--4
我在学习线程--5
我在学习线程--6
我在学习线程--7
我在学习线程--8
我在学习线程--9
我在学习线程--10
我在学习线程--11
我在学习线程--12
我在学习线程--13
我在看代码————0
我在学习线程--14
我在学习线程--15
我在看代码————1
我在学习线程--16
我在看代码————2
我在看代码————3
我在看代码————4
我在学习线程--17
我在看代码————5
我在看代码————6
我在看代码————7
我在看代码————8
我在看代码————9
我在看代码————10
我在看代码————11
我在看代码————12
我在看代码————13
我在看代码————14
我在看代码————15
我在看代码————16
我在看代码————17
我在看代码————18
我在看代码————19
我在学习线程--18
我在学习线程--19
Process finished with exit code 0
第二种:实现Runnable接口方式
步骤如下:
- 定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
- 创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正
的线程对象。 - 调用线程对象的start()方法来启动线程。
public class TestThread2 implements Runnable {
@Override
public void run() {
//run 方法线程体
for(int i=0;i<20;i++){
System.out.println("我在看代码————"+i);
}
}
public static void main(String[] args) {
//创建runnable 接口的实现类对象
TestThread2 testThread2=new TestThread2();
//创建线程对象,通过线程对象来开启我们的线程,
// Thread thread = new Thread(testThread2);
// thread.start();
//调用start()方法开启线程
new Thread(testThread2).start();
for (int i = 0; i < 200; i++) {
System.out.println("我在学习线程--"+i);
}
}
}
输出:
我在看代码————0
我在看代码————1
我在看代码————2
我在看代码————3
我在看代码————4
我在看代码————5
我在看代码————6
我在看代码————7
我在看代码————8
我在学习线程--0
我在学习线程--1
我在学习线程--2
我在学习线程--3
我在学习线程--4
我在学习线程--5
我在学习线程--6
我在看代码————9
我在学习线程--7
我在学习线程--8
我在学习线程--9
Process finished with exit code 0
Thread和Runnable的区别
如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。 总结:
实现Runnable接口比继承Thread类所具有的优势:
- 适合多个相同的程序代码的线程去共享同一个资源。
- 可以避免java中的单继承的局限性。
- 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。
- 线程池只能放入实现Runable或Callable类线程,不能直接放入继承Thread的类。
2 线程安全
如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,这就是线程安全问题。
我们通过一个案例,演示线程的安全问题:
//多个线程同时操作同一个对象
//买火车票多例子
public class TestThread3 implements Runnable {
//票数
private int ticketNums=10;
@Override
public void run() {
while (true){
if(ticketNums<=0){
break;
}
try{
Thread.sleep(200);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->拿到了第"+ticketNums-- +"票");
}
}
public static void main(String[] args) {
TestThread3 ticket =new TestThread3();
new Thread(ticket,"小明").start();
new Thread(ticket,"小花").start();
new Thread(ticket,"黄牛党").start();
}
}
结果输出:小明–>拿到了第9票
黄牛党–>拿到了第9票
小花–>拿到了第10票
小花–>拿到了第8票
小明–>拿到了第8票
黄牛党–>拿到了第8票
小花–>拿到了第7票
小明–>拿到了第6票
黄牛党–>拿到了第5票
黄牛党–>拿到了第4票
小明–>拿到了第3票
小花–>拿到了第4票
黄牛党–>拿到了第2票
小明–>拿到了第1票
小花–>拿到了第0票
黄牛党–>拿到了第-1票
发现程序出现了两个问题:
- 相同的票数
- 不存在的票,比如0票与-1票,是不存在的。 这种问题,几个窗口(线程)票数不同步了,这种问题称为线程不安全。
2.2线程同步
当我们使用多个线程访问同一资源的时候,且多个线程中对资源有写的操作,就容易出现线程安全问题。
要解决上述多线程并发访问一个资源的安全性问题:也就是解决重复票与不存在票问题,Java中提供了同步机制 (synchronized)来解决。
为了保证每个线程都能正常执行原子操作,Java引入了线程同步机制。 那么怎么去使用呢?有三种方式完成同步操作:
- 同步方法 2. 同步代码块 3. 锁机制。
例子:1.同步方法
使用synchronized修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外等着。
格式: public synchronized void method(){
可能会产生线程安全问题的代码
}
public class UnsafeTicket {
public static void main(String[] args) {
TestLock2 testLock2 =new TestLock2();
new Thread(testLock2).start();
new Thread(testLock2).start();
new Thread(testLock2).start();
}
}
class TestLock2 implements Runnable{
int ticketNums=10;
@Override
public synchronized void run() {
while (true){
if(ticketNums>0){
try{
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(ticketNums--);
}else {
break;
}
结果:
10
9
8
7
6
5
4
3
2
1
2.同步代码块
同步代码块: synchronized 关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。
格式:synchronized (同步锁){
需要同步操作的代码块
}
同步锁: 对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁.
- 锁对象 可以是任意类型。
- 多个线程对象 要使用同一把锁。
代码:
public class Testlock3 {
public static void main(String[] args) {
TestLock1 testLock2 =new TestLock1();
new Thread(testLock2).start();
new Thread(testLock2).start();
new Thread(testLock2).start();
}
}
class TestLock1 implements Runnable {
int ticketNums = 10;
Object lock = new Object();
@Override
public void run() {
while (true) {
synchronized (lock) {
if (ticketNums > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(ticketNums--);
} else {
break;
}
}
}
}
}
3.lock锁
lock锁机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作,同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象。 Lock锁也称同步锁,加锁与释放锁方法化了,如下:
public void lock() :加同步锁。 public void unlock() :释放同步锁。
代码如下:
import java.util.concurrent.locks.ReentrantLock;
// 测试锁
public class TestLock {
public static void main(String[] args) {
TestLock2 testLock2 = new TestLock2();
new Thread(testLock2).start();
new Thread(testLock2).start();
new Thread(testLock2).start();
}
}
class TestLock2 implements Runnable{
int ticketNums=10;
//定义lock 锁
private final ReentrantLock lock =new ReentrantLock();
@Override
public void run() {
while (true){
try{
lock.lock();//加锁
if(ticketNums>0){
try{
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(ticketNums--);
}else {
break;
}
}finally {
lock.unlock();//解锁
}
}
}
}
synchronized 与lock 的对比
- lock是显示锁(手动开启和关闭锁)synchronized 是隐式锁,出了作用域自动释放。
- Lock 只有代码块锁,synchronized 有代码块锁和方式锁
- 使用lock锁,JVM将花费较少的时间来调整线程,性能更好,并且具有更好的扩展性
- 优先使用顺序:
Lock> 同步代码块>同步方法
本文详细介绍了Java中创建线程的四种方法,包括继承Thread类、实现Runnable接口、实现Callable接口及使用线程池。并通过案例分析了线程安全问题,探讨了同步方法、同步代码块和Lock锁三种解决线程同步的策略。
169万+

被折叠的 条评论
为什么被折叠?



